I am trying to begin writing unit tests for my angular application and hit a stopping block pretty quick as I am unsure of how exactly to mock my service in a testable way.
Is there a way to mock the REST call otherwise it would seem like I need to mirror everything within my service in my tests which doesn't seem right to me, but I am rather new to test writing so maybe this is how it is supposed to be accomplished. Any help would be greatly appreciated.
My service is as follows:
angular.module('resources.users', ['ngResource'])
.factory('User', function($resource) {
var resource = $resource('/api/index.php/users/:username', {}, {
'update': {method: 'PUT'}
});
resource.getUser = function(username, successCb) {
return resource.query({username: username}, successCb);
};
return resource;
});
My test consists thus far of:
describe('User', function() {
var mockUserResource;
beforeEach(module('resources.users'));
beforeEach(function() {
mockUserResource = sinon.stub({
getUser: function(username) {
mockUserResource.query({username: username});
},
query: function() {}
});
module(function($provide) {
$provide.value('User', mockUserResource);
})
});
describe('getUser', function() {
it('should call getUser with username', inject(function(User) {
User.getUser('test');
expect(mockUserResource.query.args[0][0]).toEqual({username: 'test'});
}));
})
});
You can mock the requests made by ngResource like this:
describe('User', function () {
var mockUserResource, $httpBackend;
beforeEach(angular.mock.module('myApp'));
beforeEach(function () {
angular.mock.inject(function ($injector) {
$httpBackend = $injector.get('$httpBackend');
mockUserResource = $injector.get('User');
})
});
describe('getUser', function () {
it('should call getUser with username', inject(function (User) {
$httpBackend.expectGET('/api/index.php/users/test')
.respond([{
username: 'test'
}]);
var result = mockUserResource.getUser('test');
$httpBackend.flush();
expect(result[0].username).toEqual('test');
}));
});
});
Demo
zsong's answer greatly helped me understand this, but I would like to expand on how it works. In case it gets edited, I list the code again here:
describe('User', function () {
var mockUserResource, $httpBackend;
beforeEach(angular.mock.module('myApp'));
beforeEach(function () {
angular.mock.inject(function ($injector) {
$httpBackend = $injector.get('$httpBackend');
mockUserResource = $injector.get('User');
})
});
describe('getUser', function () {
it('should call getUser with username', inject(function (User) {
$httpBackend.expectGET('/api/index.php/users/test')
.respond([{
username: 'test'
}]);
var result = mockUserResource.getUser('test');
$httpBackend.flush();
expect(result[0].username).toEqual('test');
}));
});
});
What's going on here?
1
beforeEach(angular.mock.module('myApp'));
We tell the Angular injector ($injector and angular.mock.inject) to inject things defined in the myApp module. You can think of it as defining a module dependency without a dependent module. Compare with how things defined in the myApp module can be injected in, say, a controller in a angular.module('myOtherApp', ['myApp']) module.
2
beforeEach(function () {
angular.mock.inject(function ($injector) {
$httpBackend = $injector.get('$httpBackend');
mockUserResource = $injector.get('User');
})
});
Before each spec, run the function ($injector) function with dependencies injected. In this case, the dependency ($injector) is resolved implicitly from the parameter name. A functionally equivalent variant of this snippet is
beforeEach(function () {
angular.mock.inject(['$httpBackend', 'User', function ($httpB, User) {
$httpBackend = $httpB;
mockUserResource = User;
}]);
});
Here we have instead declared the dependencies explicitly, and are free to use any parameter names we wish.
3
it('should call getUser with username', inject(function (User) {
Again, the test function is injected with the implicitly resolved User service as a parameter, though it isn't actually used.
Notice that this time there is no wrapper function around the inject call. inject invokes the passed function immediately if a spec is currently running, but otherwise it returns a wrapper function (see the inject docs and source code), so we don't actually need the wrapper function. Thus, we could have written the beforeEach snippet above like this:
beforeEach(angular.mock.inject(function ($injector) {
$httpBackend = $injector.get('$httpBackend');
mockUserResource = $injector.get('User');
}));
Related
I am trying to write the test cass for the factory which is returing a JSON response.
But I am getting the error:
Error: [$injector:unpr] http://errors.angularjs.org/1.4.1/$injector/unpr?p0=serviceProvider%20%3C-%20service
at Error (native)
Here is my code:
(function () {
angular.module('uspDeviceService',[]).factory('getDevice', GetDevice);
GetDevice.$inject = ['$http'];
function GetDevice($http) {
getDeviceList = function() {
return $http.get("static/test-json/devices/device-list.json");
}
return {
getDeviceList: getDeviceList
}
}
}());
Code for Test case:
describe('Get Product test', function() {
beforeEach(module('uspDeviceService'));
var service, httpBackend, getDevice ;
beforeEach(function () {
angular.mock.inject(function ($injector) {
//Injecting $http dependencies
httpBackend = $injector.get('$httpBackend');
service = $injector.get('service');
getDevice = $injector.get('getDevice');
})
});
console.log('Injection Dependencies is done');
describe('get Device List', function () {
it("should return a list of devices", inject(function () {
httpBackend.expectGET("static/test-json/devices/device-list.json").respond("Response found!");
httpBackend.flush();
}))
})
});
I am new to Angular Unit testing, can anyone please help me, where I am going wrong..
Two things that jump out at me:
Your angular.module declaration is defining a module, not getting the module. I would encourage you to split that up so that it's a fair bit more clear what your intent is.
angular.module('uspDeviceService', []);
angular.module('uspDeviceService').factory('getDevice', GetDevice);
It likely works as-is, but clarity is important.
What is...service? It's not defined anywhere in your code, and Angular can't find it either, hence the error message. You may be looking to get getDevice instead. Also, name your test variable with respect to what it actually is, so you don't confuse yourself.
// defined above
var getDevice;
// while injecting
getDevice = $injector.get('getDevice');
Supposing that you have an angularjs controller myController defined in myModule. The controller do some action when the api call is success and shows a flash message when api returns success = false. The your controller code would be something like
angular.module('myModule')
.controller( 'myController', function ( $scope,flashService, Api ) {
Api.get_list().$promise.then(function(data){
if(data.success) {
$scope.data = data.response
}
else{
flashService.createFlash(data.message, "danger");
}
});
});
Now to test both success = true and success = false we
describe('myController', function(){
var $rootScope, $httpBackend, controller, flashService;
var apilink = 'http://apilink';
beforeEach(module('myModule'));
beforeEach(inject(function(_$httpBackend_,_$rootScope_, _$controller_, _flashService_) {
$rootScope = _$rootScope_;
$httpBackend = _$httpBackend_;
flashService = _flashService_;
controller = _$controller_("myController", {$scope: $rootScope});
}));
it('init $scope.data when success = true', function(){
$httpBackend.whenGET(apilink)
.respond(
{
success: true,
response: {}
});
$httpBackend.flush();
expect($rootScope.data).toBeDefined();
});
it('show flash when api request failure', function(){
spyOn(flashService, 'createFlash');
$httpBackend.whenGET(apilink)
.respond(
{
success: false
});
$httpBackend.flush();
expect(flashService.createFlash).toHaveBeenCalled();
});
});
You are always going to mock the response because here we are testing the javascript code behaviour and we are not concerned with the Api. You can see when success the data is initialized and when success is false createFlash is called.
As far as test for factory is concerned you can do
describe('Get Product test', function() {
beforeEach(module('uspDeviceService'));
var service, httpBackend, getDevice ;
beforeEach(function () {
inject(function ($injector) {
httpBackend = $injector.get('$httpBackend');
service = $injector.get('service');
getDevice = $injector.get('getDevice');
});
});
describe('get Device List', function () {
it("should return a list of devices", inject(function () {
httpBackend.expectGET("static/test-json/devices/device- list.json").respond("Response found!");
var result = getDevice.getDeviceList();
httpBackend.flush();
expect(result).toEqual('Response found!');
}));
});
});
I am following the angular-test-patterns guide, and I get it working with my first controller test. But when I write the next test, I get the error:
TypeError: 'undefined' is not an object (evaluating '$scope.pages.$promise')
The problem then I know is the following line:
$scope.busy = $scope.pages.$promise;
But I don't know how to deal with this, especially since I am very new in test issues with JavaScript. I looking for a correct and viable way of doing this, to point me in the right direction.
The controller:
angular.module('webvisor')
.controller('page-list-controller', function($scope,Page){
$scope.pages = Page.query();
$scope.busy = $scope.pages.$promise;
});
Service:
angular.module('webvisor').
factory('Page', ['$resource', 'apiRoot', function($resource, apiRoot) {
var apiUrl = apiRoot + 'pages/:id/:action/#';
return $resource(apiUrl,
{id: '#id'},
{update: {method: 'PUT'}}
);
}]);
Test:
'use strict';
describe('Controller: page-list-controller', function () {
var ctrl, scope, rootScope, Page;
beforeEach(function () {
module('webvisor');
module(function ($provide) {
$provide.value('Page', new MockPage());
});
inject(function ($controller, _Page_) {
scope = {};
rootScope = {};
Page = _Page_;
ctrl = $controller('page-list-controller', {
$scope: scope,
$rootScope: rootScope
});
});
});
it('should exist', function () {
expect(!!ctrl).toBe(true);
});
describe('when created', function () {
// Add specs
});
});
Mock:
MockPage = function () {
'use strict';
// Methods
this.query = jasmine.createSpy('query'); // I dont know if this is correct
return this;
};
With Mox, your solution would look like this:
describe('Controller: page-list-controller', function () {
var mockedPages = []; // This can be anything
beforeEach(function () {
mox
.module('webvisor')
.mockServices('Page') // Mock the Page service instead of $httpBackend!
.setupResults(function () {
return {
Page: { query: resourceResult(mockedPages) }
};
})
.run();
createScope();
createController('page-list-controller');
});
it('should get the pages', function () {
expect(this.$scope.pages).toEqual(resourceResult(mockedPages));
});
});
As you see, Mox has abstracted away all those boilerplate injections like $rootScope and $controller. Futhermore there is support for testing resources and promises out of the box.
Improvements
I advise you not to put the resource result directly on the scope, but resolve it as a promise:
$scope.busy = true;
Pages.query().$promise
.then(function (pages) {
$scope.pages = pages;
$scope.busy = false;
});
The Mox test is just this:
expect(this.$scope.busy).toBe(true);
this.$scope.$digest(); // Resolve the promise
expect(this.$scope.pages).toBe(mockedPages);
expect(this.$scope.busy).toBe(false);
NB: You also can store the result of createScope() into a $scope var and reuse that everywhere, instead of accessing this.$scope.
After some research and many try and error cases, I answer myself with a possible solution, but I expect to find some more usable and not too repetitive soon. For now, I am satisfied with this, using $httpBackend.
Test:
'use strict';
describe('Controller: page-list-controller', function () {
var ctrl, scope, rootScope, httpBackend, url;
beforeEach(function () {
module('webvisor');
inject(function ($controller, $httpBackend, apiRoot) {
scope = {};
rootScope = {};
httpBackend = $httpBackend;
url = apiRoot + 'pages/#';
ctrl = $controller('page-list-controller', {
$scope: scope,
$rootScope: rootScope
});
});
});
it('should exist', function () {
expect(!!ctrl).toBe(true);
});
describe('when created', function () {
it('should get pages', function () {
var response = [{ 'name': 'Page1' }, { 'name': 'Page2' }];
httpBackend.expectGET(url).respond(200, response);
httpBackend.flush();
expect(scope.pages.length).toBe(2);
});
});
});
I found this solution reading this question. This work very well, and for now, satisfied me. In future, I tried somethig like those:
angular-easy-test
mox
I'm using Jasmine to unit test an Angular controller which has a method that runs asynchronously. I was able to successfully inject dependencies into the controller but I had to change up my approach to deal with the async because my test would run before the data was loaded. I'm currently trying to spy on the mock dependency and use andCallThrough() but it's causing the error TypeError: undefined is not a function.
Here's my controller...
myApp.controller('myController', function($scope, users) {
$scope.user = {};
users.current.get().then(function(user) {
$scope.user = user;
});
});
and my test.js...
describe('myController', function () {
var scope, createController, mockUsers, deferred;
beforeEach(module("myApp"));
beforeEach(inject(function ($rootScope, $controller, $q) {
mockUsers = {
current: {
get: function () {
deferred = $q.defer();
return deferred.promise;
}
}
};
spyOn(mockUsers.current, 'get').andCallThrough();
scope = $rootScope.$new();
createController = function () {
return $controller('myController', {
$scope: scope,
users: mockUsers
});
};
}));
it('should work', function () {
var ctrl = createController();
deferred.resolve('me');
scope.$digest();
expect(mockUsers.current.get).toHaveBeenCalled();
expect(scope.user).toBe('me');
});
});
If there is a better approach to this type of testing please let me know, thank you.
Try
spyOn(mockUsers.current, 'get').and.callThrough();
Depends on the version you have used: on newer versions andCallThroungh() is inside the object and.
Here the documentation http://jasmine.github.io/2.0/introduction.html
I have a ParseService, that I would like to mock in order test all the controllers that are using it, I have been reading about jasmine spies but it is still unclear for me. Could anybody give me an example of how to mock a custom service and use it in the Controller test?
Right now I have a Controller that uses a Service to insert a book:
BookCrossingApp.controller('AddBookCtrl', function ($scope, DataService, $location) {
$scope.registerNewBook = function (book) {
DataService.registerBook(book, function (isResult, result) {
$scope.$apply(function () {
$scope.registerResult = isResult ? "Success" : result;
});
if (isResult) {
//$scope.registerResult = "Success";
$location.path('/main');
}
else {
$scope.registerResult = "Fail!";
//$location.path('/');
}
});
};
});
The service is like this:
angular.module('DataServices', [])
/**
* Parse Service
* Use Parse.com as a back-end for the application.
*/
.factory('ParseService', function () {
var ParseService = {
name: "Parse",
registerBook: function registerBook(bookk, callback) {
var book = new Book();
book.set("title", bookk.title);
book.set("description", bookk.Description);
book.set("registrationId", bookk.RegistrationId);
var newAcl = new Parse.ACL(Parse.User.current());
newAcl.setPublicReadAccess(true);
book.setACL(newAcl);
book.save(null, {
success: function (book) {
// The object was saved successfully.
callback(true, null);
},
error: function (book, error) {
// The save failed.
// error is a Parse.Error with an error code and description.
callback(false, error);
}
});
}
};
return ParseService;
});
And my test so far look like this:
describe('Controller: AddBookCtrl', function() {
// // load the controller's module
beforeEach(module('BookCrossingApp'));
var AddBookCtrl, scope, book;
// Initialize the controller and a mock scope
beforeEach(inject(function($controller, $rootScope) {
scope = $rootScope;
book = {title: "fooTitle13"};
AddBookCtrl = $controller('AddBookCtrl', {
$scope: scope
});
}));
it('should call Parse Service method', function () {
//We need to get the injector from angular
var $injector = angular.injector([ 'DataServices' ]);
//We get the service from the injector that we have called
var mockService = $injector.get( 'ParseService' );
mockService.registerBook = jasmine.createSpy("registerBook");
scope.registerNewBook(book);
//With this call we SPY the method registerBook of our mockservice
//we have to make sure that the register book have been called after the call of our Controller
expect(mockService.registerBook).toHaveBeenCalled();
});
it('Dummy test', function () {
expect(true).toBe(true);
});
});
Right now the test is failing:
Expected spy registerBook to have been called.
Error: Expected spy registerBook to have been called.
What I am doing wrong?
What I was doing wrong is not injecting the Mocked Service into the controller in the beforeEach:
describe('Controller: AddBookCtrl', function() {
var scope;
var ParseServiceMock;
var AddBookCtrl;
// load the controller's module
beforeEach(module('BookCrossingApp'));
// define the mock Parse service
beforeEach(function() {
ParseServiceMock = {
registerBook: function(book) {},
getBookRegistrationId: function() {}
};
});
// inject the required services and instantiate the controller
beforeEach(inject(function($rootScope, $controller) {
scope = $rootScope.$new();
AddBookCtrl = $controller('AddBookCtrl', {
$scope: scope,
DataService: ParseServiceMock
});
}));
it('should call registerBook Parse Service method', function () {
var book = {title: "fooTitle"}
spyOn(ParseServiceMock, 'registerBook').andCallThrough();
//spyOn(ParseServiceMock, 'getBookRegistrationId').andCallThrough();
scope.registerNewBook(book);
expect(ParseServiceMock.registerBook).toHaveBeenCalled();
//expect(ParseServiceMock.getBookRegistrationId).toHaveBeenCalled();
});
});
You can inject your service and then use spyOn.and.returnValue() like this:
beforeEach(angular.mock.module('yourModule'));
beforeEach(angular.mock.inject(function($rootScope, $controller, ParseService) {
mock = {
$scope: $rootScope.$new(),
ParseService: ParseService
};
$controller('AddBookCtrl', mock);
}));
it('should call Parse Service method', function () {
spyOn(mock.ParseService, "registerBook").and.returnValue({id: 3});
mock.$scope.registerNewBook();
expect(mock.ParseService.registerBook).toHaveBeenCalled();
});
Following Javito's answer 4 years after-the-fact. Jasmine changed their syntax in 2.0 for calling through to real methods on spies.
Change:
spyOn(ParseServiceMock, 'registerBook').andCallThrough();
to:
spyOn(ParseServiceMock, 'registerBook').and.callThrough();
Source
Include angular-mocks.js in your project and read carefully through the following link.
I'm trying to test a controller that depends on a service I built myself. I'd like to mock this service since the service talks to the DOM.
Here's my current test:
describe('Player Controllers', function () {
beforeEach(function () {
this.addMatchers({
toEqualData: function (expected) {
return angular.equals(this.actual, expected);
}
});
});
describe('TestPSPlayerModule', function () {
var $httpBackend, scope, ctrl;
beforeEach(module('PSPlayerModule'));
beforeEach(inject(function (_$httpBackend_, $rootScope, $controller) {
$httpBackend = _$httpBackend_;
scope = $rootScope.$new();
ctrl = $controller(PlayerController, { $scope: scope });
}));
it('should request a clip url from the server when clipClicked is called', function () {
expect(1).toBe(1);
});
});
});
My controller looks like this:
w.PlayerController = function ($scope, $http, $window, speedSlider, $location) {
...
}
so it's the speedSlider I want to mock.
I had the idea to use a module I created in my test code that could provide a faked implementation of the speed slider, so I added the following to the top of the test.js file:
module('TestPSPlayerModule', []).factory('speedSlider', function () {
return = {
...
};
});
and then list that module in the beforeEach() call instead of the concrete one, but if I do that I get the following error:
Injector already created, can not register a module!
So I figure there must be a better way for me to provide a mock implementation of one of my services. Something I can perhaps use sinon.js for....
Also be sure you're not trying to do this inside an inject function call:
This will throw the error:
beforeEach(inject(function(someOtherService) {
module('theApp', function($provide) {
myMock = {foo: 'bar'};
$provide.value('myService', myServiceMock);
someOtherService.doSomething();
});
}));
This will not:
beforeEach(function() {
module('theApp', function($provide) {
myMock = {foo: 'bar'};
$provide.value('myService', myServiceMock);
});
inject(function(someOtherService) {
someOtherService.doSomething();
});
});
Make sure when you use module after its definition that you don't have the extra brackets.
So module('TestPSPlayer') instead of module('TestPSPlayer',[]).
In my case this didn't worked:
beforeEach(module('user'));
beforeEach(inject(function ($http) {
}));
beforeEach(module('community'));
beforeEach(inject(function ($controller, $rootScope) {
}));
I've changed to this to make it to work:
beforeEach(module('user'));
beforeEach(module('community'));
beforeEach(inject(function ($http) {
}));
beforeEach(inject(function ($controller, $rootScope) {
}));
If your provider does not use global init you can use the original injected provider and mock it.
in the example below the testedProvider is your controller.
var injectedProviderMock;
beforeEach(function () {
module('myModule');
});
beforeEach(inject(function (_injected_) {
injectedProviderMock = mock(_injected_);
}));
var testedProvider;
beforeEach(inject(function (_testedProvider_) {
testedProvider = _testedProvider_;
}));
it("return value from injected provider", function () {
injectedProviderMock.myFunc.andReturn('testvalue');
var res = testedProvider.executeMyFuncFromInjected();
expect(res).toBe('testvalue');
});
//mock all provider's methods
function mock(angularProviderToMock) {
for (var i = 0; i < Object.getOwnPropertyNames(angularProviderToMock).length; i++) {
spyOn(angularProviderToMock,Object.getOwnPropertyNames(angularProviderToMock)[i]);
}
return angularProviderToMock;
}