Jasmine, Karma, Angular how to write test on my Angular app? - angularjs

I have just jumped to another project and, basically, I have been asked to write Unit tests. Since I have already know Protractor for e2e testing, I now switched to Karma and Jasmine to carry out unit testing. I have already downloaded karma, jasmine, karma-jasmine, and karma-chrome-launcher. I installed angular-mocks as well so I should be ready to start. I have read many things on the internet but, now, what I really need is a concrete example of a real app to figure out how to start writing tests. I don't need easy examples but concrete examples and full explenations. Books and useful links are appreciated as well. Thanks in advance for your help/time.

describe('ServiceBeingTested Name', (): void => {
var mockFirstDependency;
var mockSecondDependency;
var TestedService;
//Mock all dependencies
beforeEach((): void => {
angular.mock.module('moduleServiceIsIn'); //Register the module which the service is in
mockFirstDependency = sinon.stub(new MockFirstDependency());//Sinon if useful for mocking
mockSecondDependency = sinon.stub(new MockSecondDependency());
angular.mock.module(($provide): void => {
$provide.value('FirstDependency', mockFirstDependency);
$provide.value('SecondDependency', mockSecondDependency);
});
});
beforeEach(inject(
['TestedService', (_TestedService_: TestedService): void => {
TestedService = _TestedService_;
}]));
//Describe each method in the service
describe('method to test', (): void => {
it("should...", () => {
//testing goes in here
expect(TestedService.someMethod()).toBe("some value");
});
});
This is a simple example of how to test an angular service. In this case the service is called TestedService.
The first thing you'll see is that three variable declarations. The first two are declared to mock out the two dependencies of this service.(Assume this service has two dependencies). The last variable declaration is going to be assigned the actual service being tested.
Now in the beforeEach:
angular.mock.module
This line registers the module in which the service you are testing is in. This line is very important.
The next two line use Sinon.js to mock the dependencies of the service being tested. I recommend looking into Sinon.js
The way it works is we have a dependency called "FirstDependency" which I created a stub of and called "MockedFirstDependency" and here I created an instance of it.
Now for the next part which (the part that includes $provide)
$provide.value('FirstDependency', mockFirstDependency);
What the above line does is it tells Angular that every time the FirstDependency service is used, instead use mockFirstDependency.
Now in the next beforeEach all I do is inject the actual service which I am testing and assign it to my global variable.
Then let the testing begin
EDIT: Testing Controllers
describe('mainCtrl', (): void => {
var $controllerConstructor;
var MainCtrlInstance;
var mockScope;
var mockState;
var mockStates;
var mockGlobalData;
beforeEach(() => {
angular.mock.module('mainCtrlModule');
mockScope = sinon.stub(new MockScope());
mockState = sinon.stub(new MockState());
mockStates = sinon.stub(new MockState());
mockGlobalData = sinon.stub(new MockGlobalData());
inject(($controller: ng.IControllerService): void => {
$controllerConstructor = $controller;
});
//Constructs the controller, all dependencies must be injected here
MainCtrlInstance = $controllerConstructor('mainCtrl',
{
'$Scope': mockScope,
'$State': mockState,
'States': mockStates,
'srvGlobalData': mockGlobalData
}
);
});
describe('Method to Tests', (): void => {
it("should...", (): void => {
//Testing Begins
expect(MainCtrlInstance.method()).toBe("some value");
});
});
});
EDIT: Testing Directives
First off you will need to install Html2JsPreprocessor with this command: npm install karma-ng-html2js-preprocessor --save-dev as stated here.
karma.conf.js
files: [
//Obviously include all of your Angular files
//but make sure to include your jQuery before angular.js
"directory/to/html/directive.html", // include html for directive
"directive.js" // file directive is contained in
"directive.spec.js"" // spec file
]
// include the directive html file to be preprocessed
preprocessors: {
'directory/to/html/directive.html': 'ng-html2js'
},
plugins : [
'karma-chrome-launcher',
'karma-jasmine',
'karma-ng-html2js-preprocessor' //include as a plugin too
],
ngHtml2JsPreprocessor: {
//this part has a lot of useful features but unfortunately I
//never got them to work, Google if you need help
},
directive.js
export class myDirectiveController {
constructor(/*dependencies for controller*/) {
//initializations
}
//other methods for directive class
}
export class myDirective implements ng.IDirective {
constructor(/*dependencies for directive*/) { }
static instance(/*dependencies*/): ng.IDirective {
return new myDirective(/*dependencies for directive*/);
}
restrict = 'E';
templateUrl = 'myDirective.html';
controller = myDirectiveController;
controllerAs = 'myDirectiveController';
scope: {};
}
angular
.module('myDirectiveModule')
.directive('myDirective', myDirective.instance);
myDirective.spec.js
describe("myDirective", () => {
//do you variable declarations but I'm leaving them out for simplicity
beforeEach(() => {
angular.mock.module(
'myDirectiveModule', //and other modules in use
'directory/to/html/directive.html'
//include directive html as a module
)
// now do your mock dependencies as you did with services
mockDependency = sinon.stub(new MockDependency());
angular.mock.module(($provide): void => {
$provide.value('dependency', mockDependency);
}
//inject $compile and $rootScope
inject(($compile, $rootScope) => {
scope = $rootScope.$new();
// your directive gets compiled here
element = angular.element("<my-directive></my-directive>");
$compile(element)(scope);
$rootScope.$digest();
directiveController = element.controller('myDirective'); //this is your directive's name defined in .directive("myDirective", ...)
});
}
describe("simple test", () => {
it("should click a link", () => {
var a = element.find("a");
a.triggerHandler('click');
//very important to call scope.$digest every you change anything in the view or the model
scope.$digest();
expect('whatever').toBe('whatever');
});
});
}
Earlier when I stated to included your jQuery file before you Angular, do this because angular.element() will produce a jQuery object on which you can use the jQuery API, but if you do not include jQuery first then you angular.element() returns a jQLite object which contains less methods.
It is also important to call scope.$digest() because that updates the bindings for your directive.

Related

Angular Can't Inject Custom Service when Unit Testing Directive

I am trying to test directives and I have looked at a couple examples but I can't find one similar to mine. My current problem is that I have a controller attached to my directive and Angular can't seem to find my custom services which are injected into the controller but it can find it's own services.
Directive
export class MyDirective implements ng.IDirective {
constructor(private clientAppState: IClientAppState) { }
static instance(clientAppState: IClientAppState): ng.IDirective {
return new LocationAccessCodeDirective(clientAppState);
}
restrict = 'E';
templateUrl = 'myDirectiveDirectory.html';
controller = MyDirectiveController;
controllerAs = 'myDirectiveController';
scope: {};
}
angular
.module('myDirective')
.directive('myDirectiveName', MyDirective.instance);
MyDirectiveController
export class MyDirectiveController implements ILocationAccessCodeScope {
constructor(protected $analytics: angulartics.IAnalyticsService,
public errorMessageService: IErrorMessageService //Custom Service
)
{
//Initializations
}
Dependency File
MyDirectiveController.$inject = [
'$analytics',
'ErrorMessageService',
];
Spec
describe("myDirective", () => {
var scope;
beforeEach(() => {
angular.mock.module(
'DirectiveModule',
'ServiceModule',
'ng',
'myDirectiveDirectory.html'
);
mockErrorMessageService = sinon.stub(new MockErrorMessageService());
angular.mock.module(($provide): void => {
$provide.value('ErrorMessageService', mockErrorMessageService);
});
inject(($compile, $rootScope) => {
scope = $rootScope.$new();
var element = angular.element("<directive-tag></directive-tag>");
var comEl = $compile(element)(scope);
$rootScope.$digest();
console.log(element[0].outerHTML);
//Note that if I remove the (scope)
//...from $compile(element)(scope)
//my directive is printed out
});
});
describe('first directive test', () => {
it('first it', () => {
console.log("it");
});
});
});
Error Message
Error: [$injector:unpr] Unknown provider: ErrorMessageServiceProvider <- errorMessageService
For some reason Angular injects its own Analytics service into my controller but it cannot find my own service and I can't seem to figure out why, and I'm pretty sure I have included all of the relevant files into my Karma.config.js file
Also note, this may be irrelevant but I tried inject a random custom service into an beforeEach block and I got the same error, but when injecting it using inline annotation it found the service, I'm not sure why this may be because I'm not minifying my code, and as you can see from the dependency list it is also being inject as a list.
The problem was that I was including my dependency file after my controller file, but for some reason it started working when I included my dependency file first.

Why anglar.mock.module does not $provide values like it $provides constants

I've looked at the documentation for angular.mock.module and a couple of examples of others using it but I seem to be running into an issue in my use-case that I don't understand.
I'm running Jasmine (2.4.1) tests with angular (1.4.9) and I have my angular app separated into multiple modules. When I attempt to mock out certain parts of my app for unit testing I want to mock out entire modules (or providers) so that I only expose the pieces I use.
Here is a very simple app that has a main module plunker which depends on plunker.service. plunker.service depends on plunker.constant.
var app = angular.module('plunker', ['plunker.service']);
app.controller('MainCtrl', function($scope, valueService, appService) {
$scope.init = function() {
$scope.appValue = valueService.getValue();
$scope.appIsRunning = appService.getStatus();
};
});
angular.module('plunker.service', ['plunker.constant'])
.service('appService', function(appSettings) {
var vm = this;
vm.getStatus = function () {
if (appSettings.isRunning) {
return true;
} else {
return false;
}
};
})
.service('valueService', function(valueSettings) {
var vm = this;
vm.getValue = function () {
return valueSettings.value;
}
});
angular.module('plunker.constant', [])
.constant('appSettings', { isRunning: true })
.constant('valueSettings', { value: 10 });
In my Jasmine tests I have a beforeEach() that registers my modules using module (aka angular.mock.module).
I have seen 3 ways of using module
string
function with $provide
object
You can see below that I use the module('plunker') (string) to register my main module and I have 3 ways of mocking out my appSettings constant (A, B, C). You will notice that the function with $provide.constant works fine but function with $provide.value does not and object does not.
beforeEach(function() {
module('plunker');
function useFunction(typeofProvider) {
module(function($provide) {
$provide[typeofProvider]('appSettings', { isRunning: false });
});
}
function useObject() {
module({
appSettings: { isRunning: false }
});
}
// A. THIS WORKS! //
useFunction('constant');
// B. THIS DOES NOT //
// useFunction('value');
// C. THIS ALSO DOES NOT!! //
// useObject();
inject(function($rootScope, $controller) {
$scope = $rootScope.$new();
ctrl = $controller('MainCtrl', {
$scope: $scope
});
});
});
I have also seen people use the following syntax...
beforeEach(function() {
var mockService = function () {
var mockValue = 10;
this.value = mockValue;
};
// D.
module('a.module.name', function newProviders($provide){
$provide.service('realService', mockService);
});
});
My questions
In my test code, why does A. work but B. and C. do not?
Is D. equivalent to calling module('a.module.name'); followed by module(function newProviders($provide) { ... });? Does placing both in the same module() call have any special effects on how things are registered or is it just a shorthand? (based on the documentation it should be a shorthand)
Related to Jasmine, specifically, do all beforeEach() calls run in the same top-to-bottom order with every execution?
Here is my plunker for the above app and jasmine code
Thanks
This happens because of how Angular injector works. In fact, there are two different injectors in Angular. The one (available as $injector in config blocks) deals with service providers. Another one (available as $injector anywhere else) deals with service instances. Providers and instances are cached and stored internally.
$provide.constant('service') creates both provider and instance of name 'service' at call time.
All other types of services are lazily instantiated. They create 'serviceProvider' provider at call time, but 'service' instance is created on the first injection.
Since Angular service instance is a singleton, it refers to instance cache before the instantiation. If the instance is in the cache, it is reused and not instantiated. constant service instance is eagerly instantiated, so only another constant can override the instance.
Object properties in angular.mock.module are shortcuts for $provide.value, and useObject() equals to useFunction('value') in this example.
As long as module order stays the same,
module('a.module.name', function ($provide) { ... });
is indeed a shortcut for
module('a.module.name');
module(function ($provide) { ... });
Due to the fact that appSettings object isn't used in config blocks (the primary use of constant service), it is more convenient to make it value.

Reusing angular mocks in Jasmine tests using $provide

I wish to reuse my mocks instead of having to set them up in every unit test that has them as dependency. But I'm having a hard time figuring out how to inject them properly.
Here's my attempt at unit test setup, which of course fails because ConfigServiceMockProvider doesn't exist.
describe('LoginService tests', function () {
var LoginService;
beforeEach(module('mocks'));
beforeEach(module('services.loginService', function ($provide, _ConfigServiceMock_) {
$provide.value("ConfigService", _ConfigServiceMock_);
/* instead of having to type e.g. everywhere ConfigService is used
* $provide.value("ConfigService", { 'foobar': function(){} });
*/
});
beforeEach(inject(function (_LoginService_) {
LoginService = _LoginService_;
});
}
ConfigServiceMock
angular.module('mocks').service('ConfigServiceMock', function() {
this.init = function(){};
this.getValue = function(){};
}
I realize I probably could have ConfigServiceMock.js make a global window object, and thereby not needing to load it like this. But I feel there should be a better way.
Try something like this:
describe('Using externally defined mock', function() {
var ConfigServiceMock;
beforeEach(module('mocks'));
beforeEach(module('services.configService', function($provide) {
$provide.factory('ConfigService', function() {return ConfigServiceMock;});
}));
beforeEach(module('services.loginService'));
beforeEach(inject(function (_ConfigServiceMock_) {
ConfigServiceMock = _ConfigServiceMock_;
}));
// Do not combine this call with the one above
beforeEach(inject(function (_LoginService_) {
LoginService = _LoginService_;
}));
it('should have been given the mock', function() {
expect(ConfigServiceMock).toBeDefined('The mock should have been defined');
expect(LoginService.injectedService).toBeDefined('Something should have been injected');
expect(LoginService.injectedService).toBe(ConfigServiceMock, 'The thing injected should be the mock');
});
});
According to this answer, you have to put all of your calls to module before all of your calls to inject.
This introduces a bit of a catch-22 because you have to have the reference to your ConfigServiceMock (via inject) into the spec before you can set it on the LoginService (done in the module call)
The work-around is to set an angular factory function as the ConfigService dependency. This will cause angular to lazy load the service, and by that time you will have received your reference to the ConfigServiceMock.

Jasmine: Trying to test an AngularJS factory function

I am totally new to testing in AngularJS. I have setup karma, and am now attempting to test a certain function in a factory I have written.
Here is a snippet of my factory:
app.factory('helpersFactory', ['constants', function (constants) {
return {
someFunction: function() {
},
is24x24Icon: function (iconNum) {
return ((iconNum >= 10090 && iconNum <= 10125) ;
}
};
}]);
I then have this test:
describe('Factory: helpersFactory', function () {
beforeEach(module('ppMobi'));
var fct;
beforeEach(inject(function ($factory) {
fct = $factory('helpersFactory');
}));
it('should detect iconNum 10090 is a 24 x 24 icon', function () {
var iconNum = 10090;
var is24x24Icon = fct.is24x24Icon(iconNum);
expect(is24x24Icon).toBeTruthy();
});
});
I get an error from Karma telling me it cannot read 'is24x24icon' of undefined. Therefore I can only assume my factory has not been created properly during the test. I do have a dependency on constants in the factory used by other functions. This is just an angular.constant() I have setup on my main application module.
I have found some other posts, but am unsure how to proceed, do I need to inject my constants dependency into my test?
Kind of new myself but I think you need to use the underscore name underscore trick to inject your factory:
var fct;
beforeEach(inject(function (_helpersFactory_) {
fct = _helpersFactory_;
}));
This blog uses mocha but I found it useful and the Karma stuff should be the same: https://www.airpair.com/angularjs/posts/testing-angular-with-karma
And yes you will need to inject the constants as well (the link shows how) but your posted code does not seem to use constants so you won't need it for this particular test.

How to mock service in angularAMD with karma/jasmine?

I have a project using AngularAMD/RequireJS/Karma/Jasmine, that I have the basic configuration all working, most unit tests run and pass successfully.
I cannot get a mocked service injected correctly using either angular.mock.module or angularAMD.value().
I have:
// service definition in services/MyService.js
define(['app'],
function(app) {
app.factory('myService', [ '$document', function($document) {
function add(html) {
$document.find('body').append(html);
}
return { add: add };
}]);
}
);
// test
define(['angularAMD', 'angular-mocks', 'app', 'services/MyService'],
function(aamd, mocks, app) {
describe('MyService', function() {
var myBodyMock = {
append: function() {}
};
var myDocumentMock = {
find: function(sel) {
// this never gets called
console.log('selector: ' + sel);
return myBodyMock;
}
};
var svc;
beforeEach(function() {
// try standard way to mock a service through ng-mock
mocks.module(function($provide) {
$provide.value('$document', myDocumentMock);
});
// hedge my bets - try overriding in aamd as well as ng-mock
aamd.value('$document', myDocumentMock);
});
beforeEach(function() {
aamd.inject(['myService',
function(myService) {
svc = myService;
}]);
});
it('should work', function() {
// use svc expecting it to have injected mock of $document.
spyOn(myDocumentMock, 'find').andCallThrough();
spyOn(myBodyMock, 'append');
svc.add('<p></p>');
expect(myDocumentMock.find).toHaveBeenCalledWith('body');
expect(myBockMock.append).toHaveBeenCalledWith('<p></p>');
});
});
}
);
Does anyone know where I'm going wrong ? Any help would be much appreciated.
Angular isn't asynchronous, I think is not a good ideia use both. If you're trying to reach to a good modularization method, okay, but use the RequireJS optimizer to build everything before you put this on your browser, and about the tests, I think you can just use RequireJS optimizer to build your modules before, it will let you free from "CommonJS environment even in tests".
Looks like it'll be an issue with variable scopes, karma is very finicky about that. I think you should initialize your mock objects globally, then set them in the beforeEach.
The top line of my test files always looks something like:
var bodyMock, svcMock, foo, bar
Then in the beforeEach'es I set the values
Edit: Since bodyMock is only a scope variable, at the point where the tests are actually running and the browser is looking for an object 'bodyMock', it can't find anything.

Resources