jasmine: Mocking angular service yields timeout - angularjs

I am trying to test an angular service. The service is using another service and I would like to mock that one.
Let's say I have a service myService that depends on the service myOtherService. When just testing the application I get
Unknown provider: myOtherServiceProvider <- myOtherService <- myService
This is because I have not included the file specifing the service in the unit test. And I also don't want because I would like to mock this service.
I have come across this reference and have tried to with something like that:
describe('Service: myService', function() {
var myService;
beforeEach(function(){module('myApp');});
beforeEach(function($provide){
module(function($provide) {
$provide.service('myOtherService', function() {
this.doSomething = function(){
//...
}
});
});
});
beforeEach(inject(function(_myService_) {
myService = _myService_;
}));
describe('Duck Typing', function() {
it('should contain a doSomething() API function', function(){
expect(angular.isFunction(myService.doSomething)).toBe(true);
});
});
});
But when running the unit test I get the following error:
Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
So I would like to learn what is wrong here and how I can mock the service dependency correctly.
Thanks!

Related

How to test an error response in angular's $httpBackend?

We are using angular 1.2.x (we have to due to IE8). We are testing with Karma and Jasmine. I want to test the behavior of my modules, in case the server responds with an error. According to the angular documentation, I should just simply prepare the $httpBackend mock like this (exactly as I'd expect):
authRequestHandler = $httpBackend.when('GET', '/auth.py');
// Notice how you can change the response even after it was set
authRequestHandler.respond(401, '');
This is what I am doing in my test:
beforeEach(inject(function($injector) {
keepSessionAliveService = $injector.get('keepSessionAliveService');
$httpBackend = $injector.get('$httpBackend');
$interval = $injector.get('$interval');
}));
(...)
describe('rejected keep alive request', function() {
beforeEach(function() {
spyOn(authStorageMock, 'get');
spyOn(authStorageMock, 'set');
$httpBackend.when('POST', keepAliveUrl).respond(500, '');
keepSessionAliveService.start('sessionId');
$interval.flush(90*60*1001);
$httpBackend.flush();
});
it('should not add the session id to the storage', function() {
expect(authStorageMock.set).not.toHaveBeenCalled();
});
});
But the test fails, because the mock function is being called and I can see in the code coverage that it never runs into the error function I pass to the §promise.then as second argument.
Apparently I am doing something wrong here. Could it have to with the older angular version we're using?
Any help would be appreciated!
Something like this:
it("should receive an Ajax error", function() {
spyOn($, "ajax").andCallFake(function(e) {
e.error({});
});
var callbacks = {
displayErrorMessage : jasmine.createSpy()
};
sendRequest(callbacks, configuration);
expect(callbacks.displayErrorMessage).toHaveBeenCalled();

Spying on a wired service in angular karma test

Is it possible to spy on a service in a karma test that was wired by Angular?
Example: myService is the unit under test. thirdParty stands for a third party service that should be spied on.
.service('thirdParty', function() {
return {
hello: function() {
return 'hello world';
}
}
})
.service('myService', function(thirdParty) {
return {
world: function() {
return thirdParty.hello();
}
}
})
In my karma test I would like to spy on thirdParty service and call the real service:
describe('spy', function() {
var thirdParty, myService;
beforeEach(inject(function(_thirdParty_, _myService_) {
myService = _myService_;
thirdParty = _thirdParty_;
spyOn(thirdParty, 'hello').andCallThrough();
}));
it('should be called in myService', function() {
expect(thirdParty.hello).toHaveBeenCalled();
expect(myService.world()).toBe('hello world');
});
})
The point is that my test should assert that
a specific method of the third party service has been called inside myService
the third party service doesn't change its internal behaviour that would lead to a an exception or unexpected result (e.g. after a library update)
The myService.world() assertion just works but as I expect myService doesn't operate on the spied thirdParty service.
The result is:
Expected spy hello to have been called.
In some tests I'm already mocking third party services with a provider and a bare mock.
So I tried to create a spying instance of cacheFactory that comes with angular-cache:
beforeEach(module('angular-cache'));
beforeEach(module(function($provide, $injector, CacheFactoryProvider) {
//CacheFactoryProvider requires $q service
var q = $injector.get('$q');
var cacheFactory = CacheFactoryProvider.$get[1](q);
spyOn(cacheFactory, 'createCache').andCallThrough();
$provide.factory('CacheFactory', cacheFactory);
}));
Now I`m facing the chicken-and-egg problem:
Error: [$injector:modulerr] Failed to instantiate module function ($provide, $injector, CacheFactoryProvider) due to:
Error: [$injector:unpr] Unknown provider: $q
I know that this example can't work but because of lack of knowledge of the internals how Angular is actually instantiating and wiring services I would like to ask the community whether my test approach is possible or even sane. Thanks for help.
Instead of
it('should be called in myService', function() {
expect(thirdParty.hello).toHaveBeenCalled();
expect(myService.world()).toBe('hello world');
});
the test should be
it('should be called in myService', function() {
expect(myService.world()).toBe('hello world');
expect(thirdParty.hello).toHaveBeenCalled();
});
Indeed, the thirdParty.hello method won't have been called until you actually call myService.world().

Unit-Testing with Karma not calling functions

I am trying to perform unit testing with Karma. I have done everything according to the documentation. When I write this part of the test that follows it never calls the last two functions.
it('should create the mock object', function (done) {
service.createObj(mockObj)
.then(test)
.catch(failTest)
.finally(done);
});
var test = function() {
expect(2).toEqual(1);
};
var failTest = function(error) {
expect(2).toEqual(1);
};
Try to inject into your beforeEach function rootScope. For example like this:
var rootScope;
beforeEach(inject(function (_$rootScope_) {
rootScope = _$rootScope_.$new();
//other injections
}));
and next invoke $digest() after your service method:
it('should create the mock object', function (done) {
service.createObj(mockObj)
.then(test)
.catch(failTest)
.finally(done);
rootScope.$digest();
});
Install angular-mocks module.
Inject module with module in beforeEach.
Inject your service with inject function in beforeEach.
Use $httpBackend to simulate your server.
Do, not forget, to make it, sync. with $http.flush().

Angular & Jasmine: how to inject services with dots in their names

I've got a service that's defined this way:
angular.module("myApp")
.factory("myService.foo", function () {
// utterly delightful code
});
I'm using Karma and Jasmine for testing. In the test, I am doing something like this for most of my server tests:
describe('Service: someService', function () {
// load the service's module
beforeEach(module('myApp'));
// instantiate service
var _someService;
beforeEach(inject([function (someService) {
_someService = someService;
}]));
it('should do something', function () {
expect(!!_someService).toBe(true);
});
});
When I try to do the same with a service named something like "myService.foo" it throws an error (of course):
describe('Service: myService.foo', function () {
// load the service's module
beforeEach(module('myApp'));
// instantiate service
var _myService;
beforeEach(inject([function (myService.foo) {
_myService = myService.foo;
}]));
it('should do something', function () {
expect(!!_myService).toBe(true);
});
});
Because of the obvious problem with the dot syntax making angular unable to infer the service name. How can I inject this service to test it? Is there some alternate syntax I'm missing?
You can use the array notation, for example:
var _myService;
beforeEach(inject(['myService.foo', function (myService) {
_myService = myService;
}]));
Note (from documentation):
It is important that the order of the string identifiers in the array
is the same as the order of argument names in the signature of the
factory function. Unless the dependencies are inferred from the
function signature, it is this array with IDs and their order that the
injector uses to determine which services and in which order to
inject.

AngularJS: how do I use an angular service during module configuration time?

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.

Resources