I have a service, 'Inputs', defined in module 'Puts', that depends on a second service, 'InputCreator'. I need to stub the InputCreator service in order to test the Inputs service.
As I understand the answer here, I should create a module containing my stub service, then create a new 'Test' module, specifying the module under test and then the stub module as dependencies. And then pull the service from the injector. Like so:
beforeEach(function() {
angular.module.('Puts'); // contains the service 'Inputs'
angular.module('Mocks',[])
.service('InputCreator',function(){
var mockInputs = {
//stubbed behaviour goes here
};
return mockInputs;
});
});
angular.module('Test',['Puts', 'Mocks'];
inject(function($injector){
Inputs = $injector.get('Inputs');
});
});
However, the injector function responds with 'unknown InputsProvider <- Inputs'.
Where have I gone astray?
Thanks!
Having figured this out, I thought I'd answer my own question. The big mistake above was using angular.module rather than angular.mock.module, that is convenience referenced as module by angular-mock. They aren't the same thing at all!
Additionally, it's enough to initialize the mock service with angular.mock.module, so long as you do it before you initialize the module under test. There's no need for this 'wrapping the modules in a third module' business as suggested in the question linked above. To wit:
describe("Test Service", function() {
var TestService, getvaluestub;
beforeEach(function() {
// create mock service
var mock = {getvalue:function(){}}
angular.module('dependencymodule',[])
.service('dependencyservice',function () {
return mock;
});
//mock the function we are stubbing, (that, in this case, returns value 4)
getvaluestub = sinon.stub(mock,'getvalue')returns(4);
//instantiate your mock service
module('dependencymodule');
//instantiate the module of the service under test,
//that depends on 'dependencyservice' mocked above
//(ie - testmodule includes the service 'testservice')
module('testmodule');
//inject your test service for testing
inject(function ($injector) {
TestService = $injector.get('testservice');
})
//tests go here.....
If the dependency module already exists, you could either still do all of the above, or you could acquire the service from the $injector, insert your spies and stubs, and >then< instantiate the service under test. It's important that the spies/stubs are set up >before< the dependent service is instantiated, or it will be instantiated without them. It looks like this:
describe("Test Service", function() {
var TestService, DependencyService, getvaluestub;
beforeEach(function() {
// these modules are specified in the application
module('dependencymodule');
module('testmodule');
inject(function ($injector) {
DependencyService = $injector.get('testservice');
getvaluestub = sinon.stub(DependencyService,'getvalue').returns(4);
OtherService = $injector.get('otherservice');
})
});
// test go here
So, there you go. Hopefully this is useful to someone who searches for 'Injecting mocks into angular services'.
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'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.
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().
I have angular modules:
var app = angular.module("SearchUI",[]);
in it, I have a service "configService", that maintains bunch of config params:
app.provider("configService",function(){
//stuff here
})
I have ran the jasmine unit tests in configService fineL
describe('configService',function(){
var configService,$httpBackend;
beforeEach(module('SearchUI'));
beforeEach(inject(function(_configService_,$injector){
configService = _configService_;
$httpBackend = $injector.get("$httpBackend");
}));
it('should have default values for configService', function(){
expect(configService.getDefaultSearch()).toEqual(['abstract','title','keyword','keywordplus'
]);
});
//other stuff
all tests pass fine.
however, I am not understanding how to maintain that injection in another service:
i.e in my application:
app.service("SearchService",function($http,$log,configService,$q){
//stuff
search_params = configService.getDefaultSearch();
})
my spec:
describe('SearchService',function(){
var searchService,configService;
beforeEach(module('SearchUI'));
beforeEach(inject(function(_configService_,_SearchService_){
configService = _configService_;
searchService = _SearchService_;
}));
it('SearchService should return results', function(){
var waiting = searchService.SimpleSearch("card","wos",0);
//other stuff
the spec fails because in simplesearch function requires this:
search_params = configService.getDefaultSearch(); //get the default search parameters
my question is, how do I inject the required service in to the ANOTHER service?
Services are simply JavaScript classes and you can create an instance of them without using angular's inject mechanism to facilitate your dependency injection. Instead you can simply create a new instance of the class yourself while supplying the parameters which are required.
Currently you are creating your service via an inline function:
app.service("SearchService",function($http,$log,configService,$q){...
Instead of that by making a small adjustment which will separate out the declaration of the service from its injection into the angular module. Doing so will allow you to gain access from your test to the service class.
function SearchService($http, configService){...
app.service("SearchService", SearchService);
From your test you suite you can prepare your injectables in your beforeEach preprocessor:
describe('configService',function(){
var configService, httpBackend;
beforeEach(module('SearchUI'));
beforeEach(inject(function($httpBackend){
httpBackend = "$httpBackend";
configService = jasmine.createSpyObj('configService', ['getDefaultSearch']);
}));
/* helper method that I create to only have one place where the service is created
If I add a new param/dependency then I only have to change the construction once */
function createService(){
return new SearchService(httpBackend, configService);
}
});
The main reason for taking this approach to testing (controlling the dependency injection manually rather than relying on angular's implementation) is to have full control and to truly isolate the item I am trying to test.
configService and SearchService are init in module app but not in module SearchUI. Replace "beforeEach(module('SearchUI'));" by "beforeEach(module('myapp'));"
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.