Injecting a mock service for an angularjs controller test - angularjs

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;
}

Related

Defined service method is not called, instead real service's is called

I am following this video tutorial and its source is here.
I am trying to apply this test
Here is my test
describe("InStudentController", function () {
beforeEach(module("eucngts"));
var inStudentsController;
var MyInStudentsService;
var $scope;
var $q;
var deferred;
beforeEach(function () {
MyInStudentsService =
{
getInStudents: function () {
deferred = $q.defer();
return deferred.promise;
}
};
});
beforeEach(inject(function ($controller, $rootScope, _$q_) {
$q = _$q_;
$scope = $rootScope.$new();
inStudentsController = $controller('InStudentsController', {
service: MyInStudentsService
});
}));
it("should request list of inStudents", function () {
spyOn(MyInStudentsService, "getInStudents").and.callThrough();
inStudentsController.getPage(); // <-- HERE
//deferred.resolve();
$scope.$root.$digest();
expect(MyInStudentsService.getInStudents).toHaveBeenCalled();
});
});
Here is relevant controller code:
InStudentsController.prototype.getPage = function (criteria) {
var self = this;
self.showGrid = true;
self.service.getInStudents();
};
When I call getPage() on test it calls real service method instead of defined in test.
What am I doing wrong?
EDIT
I don't use scope in my controller here is generated code(I use typescript):
function InStudentsController (service) {
var self = this;
self.service = service;
}
InStudentsController.$inject = ['InStudentsService'];
angular.module("eucngts").controller("InStudentsController", InStudentsController);
According to your latest update it is clear that the name of dependency is used wrong in the test. It must be InStudentsService instead of service. When using $inject property of controller constructor only that name matters, not the formal parameter name in function. That makes minification possible
inStudentsController = $controller('InStudentsController', {
InStudentsService: MyInStudentsService
});
Right now you're not injecting a scope into the controller. I think this:
$scope = $rootScope.$new();
inStudentsController = $controller('InStudentsController', {
service: MyInStudentsService
});
Should be this:
$scope = $rootScope.$new();
$scope.service = MyInStudentsService
inStudentsController = $controller('InStudentsController', {
$scope: $scope
});
But it seems odd passing the service in on the scope. Instead, you should be declaring the controller something like this:
angular.module('myApp')
.controller('InStudentsController', function ($scope, InStudentsService) {
...
});
And then the service would be injected like so:
$scope = $rootScope.$new();
inStudentsController = $controller('InStudentsController', {
$scope: $scope,
InStudentsService: MyInStudentsService
});

AngularJS E2E/functional test controller

I just start with tests in AngularJS. Please help me to fix it.
My cript
angular.module('test', [])
.controller('ctrl', ['$scope', 'svc', function ($scope, svc) {
$scope.data = [];
svc.query()
.then(function (data) {
$scope.data = data;
});
}]);
and test spec
describe('ctrl', function () {
var ctrl, scope, svc, def, data = [{name: 'test'}];
beforeEach(module('test'));
beforeEach(inject(function($controller, $rootScope, $q) {
svc = {
query: function () {
def = $q.defer();
return def.promise;
}
};
var a=jasmine.createSpy(svc, 'query');
scope = $rootScope.$new();
controller = $controller('ctrl', {
$scope: scope,
svc: svc
});
}));
it('should assign data to scope', function () {
def.resolve(data);
scope.$digest();
expect(svc.query).toHaveBeenCalled();
expect(scope.data).toBe(data);
});
});
It fail:Error: Expected a spy, but got Function. in http://cdn.jsdelivr.net/jasmine/2.0.0/jasmine.js (line 2125). Can you help me
You are getting that error because its failing on expect method. expect method is expecting a spy to be passed in but its not. To fix this problem do:
spyOn(svc, 'query').andCallThrough();
You're creating a spy using createSpy(), which returns a function you can spy on, but you nere use it. You're making your life more complex than it should be. Just let angular inject the real service, and spy on its query() function. Also, use $q.when() to create a resolved promise.
describe('ctrl', function () {
var scope, svc;
var data = [{name: 'test'}];
beforeEach(module('test'));
beforeEach(inject(function($controller, $rootScope, $q, _svc_) {
svc = _svc_;
spyOn(svc, 'query').andReturn($q.when(data));
scope = $rootScope.$new();
$controller('ctrl', {
$scope: scope,
});
}));
it('should assign data to scope', function () {
scope.$digest();
expect(svc.query).toHaveBeenCalled();
expect(scope.data).toBe(data);
});
});

Unit test AngularJS controller that inherits from a base controller via $controller

The scenario is I have a ChildCtrl controller that inherits from BaseCtrl following this inheritance pattern:
angular.module('my-module', [])
.controller('BaseCtrl', function ($scope, frobnicate) {
console.log('BaseCtrl instantiated');
$scope.foo = frobnicate();
// do a bunch of stuff
})
.controller('ChildCtrl', function ($controller, $scope) {
$controller('BaseCtrl', {
$scope: $scope,
frobnicate: function () {
return 123;
}
});
});
Assuming BaseCtrl does a bunch of stuff and is already well tested, I want to test that ChildCtrl instantiates BaseCtrl with certain arguments. My initial thought was something along these lines:
describe("ChildCtrl", function () {
var BaseCtrl;
beforeEach(module('my-module'));
beforeEach(module(function($provide) {
BaseCtrl = jasmine.createSpy();
$provide.value('BaseCtrl', BaseCtrl);
}));
it("inherits from BaseCtrl", inject(function ($controller, $rootScope) {
$controller('ChildCtrl', { $scope: $rootScope.$new() });
expect(BaseCtrl).toHaveBeenCalled();
}));
});
However when I run the test the spy is never called and the console shows "BaseCtrl instantiated", indicating that $controller is using the actual controller instead of the instance I am providing with $provide.value().
What's the best way to test this?
So it looks like $controller doesn't search for controllers by name in the $provide.value() namespace. Instead you have to use the $controllerProvider.register() method, which is only accessible from the module.config() block. Fortunately it looks like there's a hook we can use to get access to $controllerProvider on the module under test.
The updated test code looks like:
describe("ChildCtrl", function () {
var BaseCtrl;
beforeEach(module('my-module', function ($controllerProvider) {
BaseCtrl = jasmine.createSpy();
BaseCtrl.$inject = ['$scope', 'frobnicate'];
$controllerProvider.register('BaseCtrl', BaseCtrl);
}));
beforeEach(inject(function ($controller, $rootScope) {
$controller('ChildCtrl', { $scope: $rootScope.$new() });
}));
it("inherits from BaseCtrl", inject(function ($controller, $rootScope) {
expect(BaseCtrl).toHaveBeenCalled();
}));
it("passes frobnicate() function to BaseCtrl that returns 123", function () {
var args = BaseCtrl.calls.argsFor(0);
var frobnicate = args[1];
expect(frobnicate()).toEqual(123);
});
});

Jasmine unit test asynchronous controller method

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

Mock a service in order to test a controller

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.

Resources