I have few questions with to write a proper unit test for a service using jasmine as framework and karma as test runner.
here is what i implemented in example-service.js:
export default class ExampleService {
constructor($resource, $http, $urlMatcherFactory) {
'ngInject';
this.$resource = $resource;
this.$http = $http;
this.$urlMatcherFactory = $urlMatcherFactory;
}
exampleMethodOne() {
//some code lines
}
exampleMethodTwo() {
//some code lines
}
}
ExampleService.selector = 'myExampleService';
Here what i wrote in my test example-service.test.js
let myExampleService, $httpBackend, $urlMatcherFactory;
beforeEach(() => {
angular
.module('exampleApp', ['ngResource'])
.service(ExampleService.selector, ExampleService);
angular.mock.module('exampleApp');
});
beforeEach(inject((_myExampleService_, _$httpBackend_,
_$urlMatcherFactory_) => {
myExampleService = _myExampleService_;
$httpBackend = _$httpBackend_;
$urlMatcherFactory = _$urlMatcherFactory_;
}));
i have imported the angular-mocks.js, angular-resource.js and example-service.js
when i try this scenario the console will throw a Error: [$injector:unpr] Unknown provider: $urlMatcherFactoryProvider <- $urlMatcherFactory <- myExampleService error.
please help me to solve this.
I suppose $urlMatcherFactory refers to UI router service, and UI Router wasn't loaded.
It's not recommended to use real router in unit tests because it provides extra moving parts and breaks isolation between units. As a rule of thumb, every unit but a tested one should be mocked.
$urlMatcherFactory stub methods have to be configured to return expected values. Since they are used during ExampleService construction, they should be configured prior to service instantiation:
let removeAllUrlMock;
beforeEach(() => {
removeAllUrlMock = jasmine.createSpyObj('removeAllUrl', ['format']);
$urlMatcherFactory = jasmine.createSpyObj('urlMatcherFactory', ['compile']);
$urlMatcherFactory.compile.and.returnValue(removeAllUrlMock);
angular
.module('exampleApp', ['ngResource'])
.service(ExampleService.selector, ExampleService);
angular.mock.module('exampleApp');
angular.mock.module({ $urlMatcherFactory });
});
This can be tested with:
expect($urlMatcherFactory.compile).toHaveBeenCalledOnce();
expect($urlMatcherFactory.compile).toHaveBeenCalledWith('../api/v1/user/{Id}/remove/all');
expect(myExampleService.removeAllUrl).toBe(removeAllUrlMock);
Then specific removeAllUrl calls can be mocked and tested when used:
removeAllUrlMock.format.and.returnValue('../api/v1/user/foo/remove/all');
myExampleService.removeProduct('foo');
expect(removeAllUrlMock.format).toHaveBeenCalledOnce();
expect($urlMatcherFactory.compile).toHaveBeenCalledWith(
jasmine.objectContaining({ id: 'foo' })
);
Since $urlMatcherFactory is utility service that doesn't provide much moving parts and supposed to be predictable, it can alternatively be imported and used directly, without UI router module:
import { UrlMatcherFactory } from '#uirouter/angularjs';
...
beforeEach(() => {
angular
.module('exampleApp', ['ngResource'])
.service(ExampleService.selector, ExampleService);
angular.mock.module('exampleApp');
angular.mock.module(($provide) => {
$provide.service('$urlMatcherFactory', UrlMatcherFactory });
});
});
Then it's just has to be spied:
spyOn(myExampleService, 'removeAllUrl').and.callThrough();
myExampleService.removeProduct('foo');
expect(removeAllUrlMock.format).toHaveBeenCalledOnce();
expect($urlMatcherFactory.compile).toHaveBeenCalledWith(
jasmine.objectContaining({ id: 'foo' })
);
Related
I'm trying to test a service documentViewer that depends on some other service authService
angular
.module('someModule')
.service('documentViewer', DocumentViewer);
/* #ngInject */
function DocumentViewer($q, authService) {
// ...
this.view = function(doc) {
//...
}
}
This is what my test looks like at the moment
it('test', inject(function($q) {
var doc = {
view: function() {
return $q.resolve(0);
}
};
var auth = {
refreshProfileData: function() {
return $q.resolve(0);
},
};
var viewer = createViewer(auth);
}));
function createViewer(auth) {
var viewer;
module({
authService: auth
});
inject(function(documentViewer) {
viewer = documentViewer;
});
return viewer;
}
The problem is I need to call inject to grab a $q, then use it to create my mocks, register my mocks with module, and then call inject again to grab the unit under test.
This results in
Error: Injector already created, can not register a module! in bower_components/angular-mocks/angular-mocks.js (line 2278)
I've seen lots of answers here on SO saying you can't call module after inject, but they don't offer any alternative to a scenario like the above.
What's the correct approach here?
PS: I'd like to avoid using beforeEach, I want each test to be self-contained.
module is used to define which modules will be loaded with inject and cannot be called after inject, this is chicken-egg situation.
The object accepted by module is used to define mocked services with $provide.value:
If an object literal is passed each key-value pair will be registered on the module via $provide.value, the key being the string name (or token) to associate with the value on the injector.
There can be no more than 1 function like createViewer that calls both module and inject. If this means that this kind of self-contained test is an antipattern, there is nothing that can be done about that. Angular testing works best with usual habits, including beforeEach and local variables.
In order to eliminate the dependency on $q, mocked service can be made a factory.
it('test', function () {
var authFactory = function ($q) {
return {
refreshProfileData: function() {
return $q.resolve(0);
},
};
};
// mocks defined first
module(function ($provide) {
$provide.factory('authService': authFactory);
});
var viewer;
inject(function(documentViewer) {
viewer = documentViewer;
});
// no module(...) is allowed after this point
var $q;
inject(function(_$q_) {
$q = _$q_;
});
var doc = {
view: function() {
return $q.resolve(0);
}
};
});
I'm unit testing a provider in Jasmine, which relies on another provider. There's no configuration associated with this provider. When mocking a provider, I've read you're supposed to use something like
beforeEach(module(function ($provide) {
mockInjectedProvider = { };
$provide.value('injected', mockInjectedProvider );
}));
which works fine for me when injecting a custom provider into a service. When injecting them into a provider it doesn't work though. The code doesn't fail, but what gets executed when testing is the actual provider, not the mocked one. Abstracted example below.
var mockInjectedProvider;
beforeEach(function () {
module('myModule');
});
beforeEach(module(function ($provide) {
mockInjectedProvider = {
myFunc: function() {
return "testvalue"
}
}
};
$provide.value('injected', mockInjectedProvider );
}));
beforeEach(inject(function (_base_) {
baseProvider = _base_;
}));
it("injectedProvider should be mocked", function () {
var resultFromMockedProvider = baseProvider.executeMyFuncFromInjected();
expect(resultFromMockedProvider).toEqual("testvalue");
}); // Here instead of using my mock it executes the actual dependency
In the $provide.value statement I've tried including both injected and injectedProvider, as well as using $provide.provider and mocking a $get function on it but nothing seems to work. I just can't get it to mock away the actual provider. Abstracted base provider looks like this.
(function (ng, module) {
module.provider("base",
["injectedProvider", function (injectedProvider) {
this.executeMyFuncFromInjected= function() {
return injectedProvider.myFunc(); // let's say this returns "realvalue"
}
this.$get = function () {
return this;
};
}]
);
})(window.angular, window.angular.module("myModule"));
Everything in my code is working except the Jasmine mocking.
In this case is better to just mock the return value instead of the provider.
var mockInjectedProvider;
beforeEach(function () {
module('myModule');
});
beforeEach(inject(function (_injected_) {
spyOn(_injected_, "myFunc").and.returnValue("testvalue");
}));
beforeEach(inject(function (_base_) {
baseProvider = _base_;
}));
it("injectedProvider should be mocked", function () {
var resultFromMockedProvider = baseProvider.executeMyFuncFromInjected();
expect(resultFromMockedProvider).toEqual("testvalue");
}); // Here instead of using my mock it executes the actual dependency
I have the following provider:
(function (angular) {
angular.module('app')
.provider('$_Config', ConfigProvider);
function ConfigProvider() {
.... //routes definition
}
ConfigProvider.prototype.$get = function () {
return this;
};
ConfigProvider.prototype.getRoutes = function() {...}
//other prototype functions
})(angular);
In app.js I am using it like this:
app.config(function ($routeProvider, $_ConfigProvider) {
var routes = $_ConfigProvider.getRoutes();
routes.forEach(function(route) {
$routeProvider
.when(route.route, {
.....
})
}
Every thing work fine till it gets to testing. Here is my test:
describe('Provider: $_ConfigProvider', function () {
// load the providers module
beforeEach(module('app'));
// instantiate provider
var $_ConfigProvider;
beforeEach(inject(function (_$_Config_) {
$_ConfigProvider = _$_Config_;
}));
it('Should verify getRoutes function', function () {
var routes = $_ConfigProvider.getRoutes();
expect(Object.prototype.toString.call(routes) === '[object Array]').toBe(true);
});
});
When running the test I am getting the following error:
Error: [$injector:modulerr] Failed to instantiate module app due to:
Error: [$injector:unpr] Unknown provider: $_ConfigProvider
Note: The $_ConfigProvider is injected correctly during run-time.
You are likely not including the file where the provider is defined in in your karma.conf.js dependencies list. See this question:
Include dependencies in Karma test file for Angular app?
I would rename the $_Config to something else, '$' is usually reserved for angular-specific components.
I am using this strategy to lazy-load stuff with RequireJS in my AngularJS app:
define([
'src/services/dependency_resolver', // resolves promise when dependencies are `require`d
'json!modules.json'
], function (dependencyResolver, modules) {
var app = angular.module('myApp', [ 'ngRoute' ]);
app.config(function ($controllerProvider, $routeProvider) {
app.lazy = {
controller: $controllerProvider.register
// <...> other providers
};
angular.forEach(modules, function (moduleConfig) {
angular.forEach(moduleConfig.routes, function (route) {
$routeProvider.when(route.path, {
templateUrl: route.templateUrl,
controller: route.controller,
resolve: dependencyResolver(moduleConfig.dependencies)
});
});
});
});
return app;
});
But I'm not sure what is the correct way test a lazy-loaded controller. It is registered like this:
define(['src/app'], function (app) {
app.lazy.controller('MainCtrl', function () {
//
});
});
And this is my current spec:
describe('`MainCtrl` controller', function () {
var Ctrl,
$scope;
beforeEach(angular.mock.module('myApp'));
beforeEach(function (done) {
require(['module/main'], done);
});
beforeEach(function () {
angular.mock.inject(function ($rootScope, $controller) {
$scope = $rootScope.$new();
Ctrl = $controller('MainCtrl', {
$scope: $scope
});
});
});
it('should ...', function () {
console.log(Ctrl);
});
});
With this spec, an error occurs when controller is being registered, because app.lazy is undefined.
So the question is how to test such controllers?
Cheers!
I was experiencing a similar problem when writing my unit test using the "lazy" property to register my controller. The problem with this approach is that when in the context of a unit test, the module config block will not be executed and as a result, app.lazy will resolve to undefined.
To solve your problem, instead of using provider registration methods to set your properties of app.lazy, the provider registration method should be used to override their counterparts on the module. In other words, your config block should now become:
`app.config(function ($controllerProvider, $routeProvider) {
app.controller = $controllerProvider.register
// <...> other providers
.......
}`
Instead of register your controller using (app.lazy):
`define(['src/app'], function (app) {
app.lazy.controller('MainCtrl', function () {
//
});
});`
you can just define like this:
`define(['src/app'], function (app) {
app.controller('MainCtrl', function () {
//
});
});`
And this should work! Hopefully this can help, and please let me know if this works out or not.
First of all, thank you for the reference you provided - the article is really interesting.
Author of the article is using AngularJs providers to implement his strategy. The thing is, that AngularJs doesn't have providers for 'specs'. So my opinion is that you should omit this strategy in your unit tests.
On this basis, I think, that you should add AMD to your spec file. Define your controller as a dependency in your spec. After this, you may just require all your specs somewhere in main-spec.js and launch your testing framework.
I'm trying to test a simple service but I'm getting an Unkown Provider error. Here is the service definition:
var sessionManager = angular.module('MyApp.SessionManager', ['ngResource']);
sessionManager.factory('UserService', function($resource) {
var UserService = $resource('/api/users/:key', {}, {
getNewUUID: {
method: 'GET',
params: {
action: 'getNewUserUUID'
}
}
});
return UserService;
});
and here is the test:
describe('Testing SessionManager', function() {
var userService;
beforeEach(function() {
module('MyApp.SessionManager');
inject(function($injector) {
userService = $injector.get('UserService');
});
});
it('should contain a UserService', function() {
expect(userService).toBeDefined();
});
});
I can't seem to see the problem, I know that the UserService javascript file is being called because I can get a console log at the top of the file, however if I put it in the service definition I don't see it get called. So for some reason it's like Angular is not instantiating my service?
I realized that the problem was my module MyApp.SessionManager was being replaced because I thought you could declare dependencies every time it was reopened to add a module. The code above is fine if of course the service is actually surviving up until the tests.