I'm currently getting started with angular unit testing. As the first controller I wanted to tes looked like this, I got confused.
angular.module('sgmPaperApp')
.controller('AccountCtrl', function ($mdToast, user, $firebaseArray, Ref) {
var vm = this;
vm.data = user;
vm.save = saveUser;
vm.comments = $firebaseArray(Ref.child('comments').orderByChild('person').equalTo(user.$id));
function saveUser() {
vm.data.$save().then(function () {
$mdToast.showSimple('Data saved');
});
}
});
Should I really mock all external services I use? After all that controller isn't very much more then external services and mocking the firebaseArray could be difficult.
Thanks for your advice and helping me get started with testing
You don't need to worry about what the external dependencies do, just mock their APIs.
These are the only mocks I can see. I'm going to assume you're using Jasmine
var Ref, $firebaseArray, $mdToast, user, vm;
beforeEach(function() {
Ref = jasmine.createSpyObj('Ref', ['child', 'orderByChild', 'equalTo']);
Ref.child.and.returnValue(Ref);
Ref.orderByChild.and.returnValue(Ref);
Ref.equalTo.and.returnValue(Ref);
$firebaseArray = jasmine.createSpy('$firebaseArray').and.returnValue('comments');
$mdToast = jasmine.createSpyObj('$mdToast', ['showSimple']);
user = jasmine.createSpyObj('user', ['$save']);
user.$id = 'id';
module('sgmPaperApp'); // you should consider separate modules per "thing"
inject(function($controller) {
vm = $controller('AccountCtrl', {
$mdToast: $mdToast,
user: user,
$firebaseArray: $firebaseArray,
Ref: Ref
});
});
});
Then you can easily create your tests
it('assigns a bunch of stuff on creation', function() {
expect(vm.data).toBe(user);
expect(vm.comments).toEqual('comments'); // that's what the mock returns
expect(Ref.child).toHaveBeenCalledWith('comments');
expect(Ref.orderByChild).toHaveBeenCalledWith('person');
expect(Ref.equalTo).toHaveBeenCalledWith(user.$id);
expect($firebaseArray).toHaveBeenCalledWith(Ref);
});
You can even test promise based methods like saveUser
it('saves the user and makes some toast', inject(function($q, $rootScope) {
user.$save.and.returnValue($q.when()); // an empty, resolved promise
vm.saveUser();
expect(user.$save).toHaveBeenCalled();
expect($mdToast.showSimple).not.toHaveBeenCalled(); // because the promise hasn't resolved yet
$rootScope.$apply(); // resolves promises
expect($mdToast.showSimple).toHaveBeenCalledWith('Data saved');
}));
So to answer the question we need to consider what we're actually trying to do. If we are trying to unit test, then yes, we need to mock all dependencies.
Mocking your dependencies won't be hard though. You only need to mock what you're using.
For example, $firebaseArray starts off as a function that receives a paramter, we know that much:
var mockFirebaseArray = function(ref) {
};
Next, before we can finish it, we need to mock the Ref:
var mockRef = {
child: function(path) {
this.orderByChild = function(path) {
this.equalTo = function(val) {
};
return this;
};
return this;
}
};
With these things in place we can decide how the test will "pass". We could just use spies. Or, we could set local variables that we can assert later on our way through.
Spies are my preferred method because you can even verify they were called with specific values:
expect(mockFirebaseArray).toHaveBeenCalled();
expect(mockRef.child).toHaveBeenCalledWith('comments');
Now, if you're wanting to write an integration test that's different. In that case I'd still use spies, but you'd actually be executing those dependencies. Generally speaking there is no need to test your dependencies because they should be tested in isolation as well. Furthermore, there is less need to test other people's API's if they are from trustworthy sources.
Related
Let assume I have some service in AngularJS: ComplexService. It performs complex operations on init and has got a complex interface...
In Karma/Jasmine tests, to simplify other components unit tests, I have defined a mock globally[1] (outside of all describe declarations in Karma global scope):
beforeEach(function () {
angular.mock.module('MYMODULE', function ($provide) {
$provide.value('ComplexService', buildComplexServiceMock());
});
});
[1](The reason of that decision was to avoid declaring it in each test suite again -we have about 50 of them and each eventually uses the service indirectly or by default)
Let now suppose, that I decided to Write some unit test for the complex service.
My question is: Does it exist a way to access the real service now? (not mock)
My temporary solution is to make my service accessible in global scope too and access it directly:
function ComplexService(Other, Dependencies) {
//code here
}
angular.module('MYMODULE')
.service('ComplexService', ['Other', 'Dependencies', ComplexService]);
window.ComplexService = ComplexService;
But I am not happy with it. (I don't want production code to be accessible globally, maybe except in tests)
Can somebody please, give me some clue?
Edit
Another thing I would like to avoid if possible is specifying ComplexService dependencies in test directly (in a case the order would change in future)
Temporary solution which is bad:
let complexServiceTestable;
beforeEach(function () {
inject(function (Other, Dependencies) {
//If order of dependencies would change, I will have to modify following line:
complexServiceTestable = window.ComplexService(Other, Dependencies);
});
});
Something I would appreciate most if possible:
let complexServiceTestable;
beforeEach(function () {
angular.mock.module('MYMODULE', function ($provide) {
//some magic here
});
});
beforeEach(function () {
inject(function (ComplexService) {
complexServiceTestable = ComplexService;
});
});
You could do is to explicity import the real service in your test and override the $provide mock with the real one:
import ComplexService from '../your-complex-service-path/ComplexService';
describe('....', function(){
beforeEach(function () {
angular.mock.module('MYMODULE', function ($provide) {
$provide.value('ComplexService', ComplexService);
});
});
});
I understand the design decision but maybe the best thing would be to make a factory capable of injecting the $provide mocks of any Service passed as a parameter, name or path, It can be a little tricky but It might ended up being a more maintainable and descriptive approach.
Thanks to #MatiasFernandesMartinez hints in comments, after some experiments I finally reached working solution (using provider):
In global Karma context:
beforeEach(function () {
angular.mock.module('MYMODULE', function ($provide, ComplexServiceProvider) {
$provide.value('ComplexServiceBackup', ComplexServiceProvider);
$provide.value('ComplexService', buildComplexServiceMock());
});
});
In ComplexService tests:
describe('ComplexService', function () {
let complexServiceTestable;
beforeEach(function () {
inject(function (ComplexServiceBackup) {
complexServiceTestable = ComplexServiceBackup.$get();
});
});
});
I'm having troubling testing a controller's value that's set within a promise returned by a service. I'm using Sinon to stub the service (Karma to run the tests, Mocha as the framework, Chai for assertions).
I'm less interested in a quick fix than I am in understanding the problem. I've read around quite a bit, and I have some of my notes below the code and the test.
Here's the code.
.controller('NavCtrl', function (NavService) {
var vm = this;
NavService.getNav()
.then(function(response){
vm.nav = response.data;
});
})
.service('NavService', ['$http', function ($http) {
this.getNav = function () {
return $http.get('_routes');
};
}]);
Here's the test:
describe('NavCtrl', function () {
var scope;
var controller;
var NavService;
var $q;
beforeEach(module('nav'));
beforeEach(inject(function($rootScope, $controller, _$q_, _NavService_){
NavService = _NavService_;
scope = $rootScope.$new();
controller = $controller;
}));
it('should have some data', function () {
var stub = sinon.stub(NavService, 'getNav').returns($q.when({
response: {
data: 'data'
}
}));
var vm = controller("NavCtrl", {
$scope: scope,
NavService: NavService
});
scope.$apply();
stub.callCount.should.equal(1);
vm.should.be.defined;
vm.nav.should.be.defined;
});
});
The stub is being called, i.e. that test passes, and vm is defined, but vm.nav never gets data and the test fails. How I'm handling the stubbed promise is, I think, the culprit. Some notes:
Based on reading elsewhere, I'm calling scope.$apply to set the value, but since scope isn't injected into the original controller, I'm not positive that will do the trick. This article points to the angular docs on $q.
Another article recommends using $timeout as what would "actually complete the promise". The article also recommends using "sinon-as-promised," something I'm not doing above. I tried, but didn't see a difference.
This Stack Overflow answer use scope.$root.$digest() because "If your scope object's value comes from the promise result, you will need to call scope.$root.$digest()". But again, same test failure. And again, this might be because I'm not using scope.
As for stubbing the promise, I also tried the sinon sandbox way, but results were the same.
I've tried rewriting the test using $scope, to make sure it's not a problem with the vm style, but the test still fails.
In the end, I could be wrong: the stub and the promise might not be the problem and it's something different and/or obvious that I've missed.
Any help is much appreciated and if I can clarify any of the above, let me know.
Sorry but a quick fix was all that you needed:
var stub = sinon.stub(NavService, 'getNav').returns($q.when({
response: {
data: 'data'
}
}));
Your promise is resolved to object containing response.data not just data
Checkout this plunk created from your code: https://plnkr.co/edit/GL1Xuf?p=preview
The extended answer
I have often fallen to the same trap. So I started to define the result returned from a method separately. Then if the method is async I wrap this result in a promise like $q.when(stubbedResult) this allow me to, easily run expectations on the actual result, because I keep the stubbed result in a variable e.g.
it('Controller should have some data', function () {
var result = {data: 'data'};
var stub = sinon.stub(NavService, 'getNav').returns($q.when(result));
var vm = controller(/* initController */);
scope.$apply();
stub.callCount.should.equal(1);
vm.nav.should.equal(result.data)
})
Also some tests debugging skill will come in handy. The easiest thing is to dump some data on the console just to check what's returned somewhere. Working with an actual debugger is preferable of course.
How to quickly catch mistakes like these:
Put a breakpoint at the $rootScope.apply() line (just before it is executed)
Put a breakpoint in the controller's NavService.getNav().then handler to see whether it is called and what it was called with
Continue with the debugger to execute the $rootScope.$apply() line. Now the debugger should hit the breakpoint set at the previous step - that's it.
I think you should use chai-as-promised
and then assert from promises like
doSomethingAsync().should.eventually.equal("foo");
or else use async await
it('should have some data', async function () {
await scope.$apply();
});
you might need to move then getNav() call in init kinda function and then test against that init function
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.
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.
See this plunkr for a live example: http://plnkr.co/edit/djQPW7g4HIuxDIm4K8RC
In the code below, the line var promise = serviceThatReturnsPromise(); is run during module configuration time, but I want to mock out the promise that is returned by the service.
Ideally I'd use the $q service to create the mock promise, but I can't do that because serviceThatReturnsPromise() is executed during module configuration time, before I can get access to $q. What's the best way to resolve this chicken and egg problem?
var app = angular.module('plunker', []);
app.factory('serviceUnderTest', function (serviceThatReturnsPromise) {
// We mock out serviceThatReturnsPromise in the test
var promise = serviceThatReturnsPromise();
return function() {
return 4;
};
});
describe('Mocking a promise', function() {
var deferredForMock, service;
beforeEach(module('plunker'));
beforeEach(module(function($provide) {
$provide.factory('serviceThatReturnsPromise', function() {
return function() {
// deferredForMock will be undefined because this is called
// when `serviceUnderTest` is $invoked (i.e. at module configuration),
// but we don't define deferredForMock until the inject() below because
// we need the $q service to create it. How to solve this chicken and
// egg problem?
return deferredForMock.promise;
}
});
}));
beforeEach(inject(function($q, serviceUnderTest) {
service = serviceUnderTest;
deferredForMock = $q.defer();
}));
it('This test won\'t even run', function() {
// we won't even get here because the serviceUnderTest
// service will fail during module configuration
expect(service()).toBe(4);
});
});
I'm not sure I like the solution much, but here it is:
http://plnkr.co/edit/uBwsJxJRjS1qqsKIx5j7?p=preview
You need to ensure that you don't instantiate "serviceUnderTest" until after you've set-up everything. Therefore, I've split the second beforeEach into two separate pieces: the first instantiates and uses $q, the second instantiates and uses serviceUnderTest.
I've also had to include the $rootScope, because Angular's promises are designed to work within a $apply() method.
Hope that helps.