Background:
I have an angular.js controller 'GetResultController' with some external dependencies:
angular.module('data-service',[]).service('DataService', DataService);
angular.module('GetResultModule', ['datatables', 'data-service']);
angular.module('GetResultModule')
.controller('GetResultController',
['$q','DTColumnBuilder','DataService', GetResultController]);
function GetResultController($q, DTColumnBuilder, DataService) {
let self = this;
self.getResult = function getResult() {
return 'positive';
}
}
Questions:
ver-1:
describe('testGetResultController', () =>{
beforeEach(angular.mock.module('datatable');
//do i need to create the mock module for 'data-service', and how to do next?
}
)
ver-2:
describe('testGetResultController', () => {
beforeEach(angular.module('datatable',[]);
beforeEach(angular.module('common-service',[]);
let c;
beforeEach(angular.mock.inject(function(_$controller_) {
c = _$controller_('GetResultController'); // is it correct to get back the controller?
}
it('test', ()=> {
expect(c).toBeDefined(); // why c is undefined?
});
})
As I want to draft a unit test to verify 'GetResultController'. How can I initialize the dependencies, mock the object to test the 'getResult' method. Do we have any experience to handle this case.
Many thanks
Related
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.
I have a service MyService with a function using the ga() event tracking call which I want to test:
angular.module('myModule').factory('MyService', [function() {
var myFunc = function() {
ga('send', 'event', 'bla');
// do some stuff
}
return {
myFunc: myFunc
}
]);
My spec file looks like this:
describe('The MyService', function () {
var MyService,
ga;
beforeEach(function () {
module('myModule');
ga = function() {};
});
beforeEach(inject(function (_MyService_) {
MyService = _MyService_;
}));
it('should do some stuff', function () {
MyService.myFunc();
// testing function
});
});
Running my tests always gives me:
ReferenceError: Can't find variable: ga
The problem is global scope of ga.
The ga variable that you create inside your tests has a local scope and will not be visible to your own service.
By using a global variable (ga) you have made unit testing difficult.
The current option would be to either create a angular service to wrap gaand use that everywhere else. Such service can be mocked too.
The other option is to override the global ga. But this will have side effects.
window.ga=function() {}
After trying different solution I finally fixed with below code.
beforeAll( ()=> {
// (<any>window).gtag=function() {} // if using gtag
(<any>window).ga=function() {}
})
Slightly out of date, but I am trying to leverage ReactGA and mocked creating an event like:
it('should do something...', () => {
const gaSpy = jest.spyOn(ReactGA, 'ga');
someService.functionThatSendsEvent({ ...necessaryParams });
expect(gaSpy).toHaveBeenCalledWith('send', 'event',
expect.objectContaining({/*whatever the event object is supposed to be*/}
);
});
This is helpful if youre sending specific data to an angular/reactjs service which is then sending it to GA.
I have a service CurrentUser that has a dependency on StorageFactory. In the constructor of this service, if StorageFactory.get returns a user, I am setting the user to that user, otherwise setting a default user value. Now, I want to test this.
I have managed to make this work, but I am not happy with the approach I am using. I got the inspiration for this approach here.
I have pasted the code below. If you prefer, I have also created a gist here. I have removed the irrelevant part of the code to avoid distraction. This is written using ES6 classes and modules, but that shouldn't make any difference to the tests.
The problem with the approach is that the mock will be used across all the tests, which may not be a bad thing but I want to control that. Is there a way to make this mock take affect only for this test?
One roadblock in finding better approach is that the mock has to be done before angular created the mock CurrentUserModule module. Is there a better way of testing this? I would appreciate any suggestions on this.
Service
import StorageFactoryModule from 'app/common/services/StorageFactory';
class CurrentUser {
constructor(StorageFactory) {
this.storageKey = 'appUser';
this.StorageFactory = StorageFactory;
this.profile = initializeUser.call(this);
function initializeUser() {
var userFromStorage = StorageFactory.get(this.storageKey);
if (userFromStorage != null) {
return userFromStorage;
} else {
// return default user
}
}
}
// more methods, that are removed for brevity
}
CurrentUser.$inject = ['StorageFactory'];
export default angular.module('CurrentUserModule', [StorageFactoryModule.name])
.service('CurrentUser', CurrentUser);
Test
import angular from 'angular';
import 'angular-mocks';
import './CurrentUser';
describe('CurrentUser', function () {
"use strict";
var user = {userid: 'abc', token: 'token'};
var storageFactoryMock = {
get: function (key) {
return user;
},
put: function (key, newUser) {
user = newUser;
},
remove: function (key) {
user = undefined;
}
};
beforeEach(function () {
angular.module('StorageFactoryModule')
.value('StorageFactory', storageFactoryMock);
angular.mock.module('CurrentUserModule');
});
it('should Initialize User from local storage if already exists there', inject(function (CurrentUser) {
expect(CurrentUser.profile).toEqual(user);
}))
});
I have managed to find a better way as below. I was initially doing it the wrong way (feels stupid now). Rather than creating a mock module, I was creating a real module that was overriding the original one, and that's why it was impacting all tests. Now, I am creating a mock module and using $provide to override StorageFactory, which will impact only the current suite.
beforeEach(function () {
angular.mock.module('StorageFactoryModule');
module(function($provide) {
$provide.value('StorageFactory', storageFactoryMock);
});
angular.mock.module('CurrentUserModule');
});
EDIT: Refactored my code and made it more flexible by creating a function that accepts a user as parameter and creates modules based on the user passed.
var correctUser = {userid: 'abc', token: 'token'};
var defaultUser = {userid: '', token: ''};
function createStorageFactoryMock(userInStorage) {
return {
get: function () {
return userInStorage;
},
put: function (key, newUser) {
userInStorage = newUser;
},
remove: function () {
userInStorage = undefined;
}
}
}
function CreateUserModule(user = correctUser) {
angular.mock.module('StorageFactoryModule');
module(function ($provide) {
$provide.value('StorageFactory', createStorageFactoryMock(user));
});
angular.mock.module('CurrentUserModule');
}
Now in my tests, I can mock different module for different scenarios, and write my tests accordingly. Any feedback is welcome.
it('should Initialize User from storageFactory if already exists in storage', function () {
CreateUserModule();
inject(function (CurrentUser) {
expect(CurrentUser.profile).toEqual(correctUser);
});
});
it('should Initialize default user if user not present in storage', function () {
CreateUserModule(null);
inject(function (CurrentUser) {
expect(CurrentUser.profile).toEqual(defaultUser);
});
});
I have a module that in its config uses a provider that I need to mock.
provider itself has register function that takes 2 arguments and $get function that returns an object, but that's not really important (I think) since I'd be mocking it anyway. Just in case here's the entire provider
module uses the provider like this:
angular.module('replenishment').config(configureReplenishmentColumnDef);
configureReplenishmentColumnDef.$inject = ['columnDefProvider'];
function configureReplenishmentColumnDef(columnDefProvider) {
let config = {
matchWhen: ((schema)=> _.get(schema, 'format') === 'replenishment'),
$get: replenishmentColDef
};
columnDefProvider.register(config);
}
replenishmentColDef.$inject = ['$q', 'schema', 'medData'];
function replenishmentColDef($q, schema, medData) {
....
}
I started putting together a spec like this (our tests written in CoffeeScript)
describe 'replenishment-module', ->
columnDefProvider = undefined
beforeEach ->
module ($provide)->
$provide.provider 'columnDef', ->
#.register = sinon.spy()
#.$get = sinon.spy()
return // had to put an explicit return here, as #DTing suggested
module 'replenishment'
inject ->
Now I don't know how to properly mock provider's methods. Can you guys please show me, maybe I need to use stubs instead of spies.
Try adding explict returns like this, I think you are getting a malformed function:
describe 'replenishment-module', ->
columnDefProvider = undefined
beforeEach ->
module ($provide)->
$provide.provider 'columnDef', ->
#.register = sinon.spy()
#.$get = sinon.spy()
return
module 'replenishment'
inject ->
I think this is what your javascript equivalent looks like:
beforeEach(function() {
module(function($provide) {
return $provide.provider('columnDef', function() {
this.register = sinon.spy();
return this.$get = sinon.spy();
});
});
which would make it so your Provider columnDef doesn't have a $get factory method:
function Something() {
return this.get = function() {
console.log("get");
};
}
var something = new Something();
something.get();
How do you write tests for something like FabricJS in a directive and service?
Example app: http://fabricjs.com/kitchensink/
I have been trying but I'm not making much progress without really bad hacks.
I want to integrate this service and directive into my https://github.com/clouddueling/angular-common repo so others can use this powerful library.
My scenario:
I'm trying to test my module that contains a service and directive. Those link my app to FabricJS. I'm having issues mocking the global fabric var that is created when you include the js file. I'm assuming then I spy on the var containing the fabric canvas.
I just need to confirm that my service is interacting with fabric correctly. I'm having trouble mocking/stubbing fabric though.
To win the bounty:
Example of a test I could use with Karma.
It's difficult as you've not provided the code you want to test. However, for testability, I would firstly create a very small factory to return the global fabric object
app.factory('fabric', function($window) {
return $window.fabric;
});
This factory can then be tested by injecting a mock $window, and checking that its fabric property is returned.
describe('Factory: fabric', function () {
// load the service's module
beforeEach(module('plunker'));
var fabric;
var fakeFabric;
beforeEach(function() {
fakeFabric = {};
});
beforeEach(module(function($provide) {
$provide.value('$window', {
fabric: fakeFabric
});
}));
beforeEach(inject(function (_fabric_) {
fabric = _fabric_;
}));
it('should return $window.fabric', function () {
expect(fabric).toBe(fakeFabric);
});
});
An example service that then uses this factory is below.
app.service('MyFabricService', function(fabric) {
this.newCanvas = function(element) {
return new fabric.Canvas(element);
}
this.newRectangle = function(options) {
return new fabric.Rect(options);
}
this.addToCanvas = function(canvas, obj) {
return canvas.add(obj);
}
});
You can then test these methods as below. The functions that return 'new' objects can be tested by creating a mock fabric object with a manually created spy that will be called as a constructor, and then using instanceof and toHaveBeenCalledWith to check how its been constructed:
// Create mock fabric object
beforeEach(function() {
mockFabric = {
Canvas: jasmine.createSpy()
}
});
// Pass it to DI system
beforeEach(module(function($provide) {
$provide.value('fabric', mockFabric);
}));
// Fetch MyFabricService
beforeEach(inject(function (_MyFabricService_) {
MyFabricService = _MyFabricService_;
}));
it('should return an instance of fabric.Canvas', function () {
var newCanvas = MyFabricService.newCanvas();
expect(newCanvas instanceof mockFabric.Canvas).toBe(true);
});
it('should pass the element to the constructor', function () {
var element = {};
var newCanvas = MyFabricService.newCanvas(element);
expect(mockFabric.Canvas).toHaveBeenCalledWith(element);
});
The addToCanvas function can be tested by creating a mock canvas object with an 'add' spy.
var canvas;
// Create mock canvas object
beforeEach(function() {
canvas = {
add: jasmine.createSpy()
}
});
// Fetch MyFabricService
beforeEach(inject(function (_MyFabricService_) {
MyFabricService = _MyFabricService_;
}));
it('should call canvas.add(obj)', function () {
var obj = {};
MyFabricService.addToCanvas(canvas, obj);
expect(canvas.add).toHaveBeenCalledWith(obj);
});
This can all be seen in action in this Plunker http://plnkr.co/edit/CTlTmtTLYPwemZassYF0?p=preview
Why would you write tests for an external dependency?
You start by assuming FabricJS just works. It's not your job to test it, and even if it were, you'd have to do byte stream comparison (that's what a canvas is, a stream of bytes interpreted as an image). Testing user input is a whole different thing. Look up Selenium.
Then you write tests for the code that produces the correct input for FabricJS.