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
Related
I am trying to mock a method in a service that returns a promise. The controller:
app.controller('MainCtrl', function($scope, foo) {
var self = this;
this.bar = "";
this.foobar = function() {
console.log('Calling the service.');
foo.fn().then(function(data) {
console.log('Received data.');
self.bar = data;
});
}
this.foobar();
});
The spec file:
angular.module('mock.foo', []).service('foo', function($q) {
var self = this;
this.fn = function() {
console.log('Fake service.')
var defer = $q.defer();
defer.resolve('Foo');
return defer.promise;
};
});
describe('controller: MainCtrl', function() {
var ctrl, foo, $scope;
beforeEach(module('app'));
// inject the mock service
beforeEach(module('mock.foo'));
beforeEach(inject(function($rootScope, $controller, _foo_) {
foo = _foo_;
$scope = $rootScope.$new();
ctrl = $controller('MainCtrl', {$scope: $scope , foo: foo });
}));
it('Should call foo fn', function() {
expect($scope.bar).toBe('Foo');
});
});
When debugging, I can see in the controller the promise object state being 1 (resolved). Yet, the success callback within then is never invoked.
The following Plunker http://plnkr.co/edit/xpiPKPdjhiaI8KEU1T5V reproduces the scenario. Any help would be greatly appreciated.
You must get a reference to $rootScope in your test and call:
$rootScope.$digest()
Your plunk, revisited:
$digest called and test passed.
Also your mock didn't return anything in the resolve, I added:
defer.resolve('Foo');
HTH
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);
});
});
My controller is:
angularMoonApp.controller('SourceController', ['$scope', '$rootScope', '$routeParams', 'fileService', function ($scope, $rootScope, $routeParams, fileService) {
$scope.init = function() {
$rootScope.currentItem = 'source';
fileService.getContents($routeParams.path).then(function(response) {
$scope.contents = response.data;
$scope.fileContents = null;
if(_.isArray($scope.contents)) {
// We have a listing of files
$scope.breadcrumbPath = response.data[0].path.split('/');
} else {
// We have one file
$scope.breadcrumbPath = response.data.path.split('/');
$scope.breadcrumbPath.push('');
$scope.fileContents = atob(response.data.content);
fileService.getCommits(response.data.path).then(function(response) {
$scope.commits = response.data;
});
}
});
}
$scope.init();
}]);
My test is pretty simple:
(function() {
describe('SourceController', function() {
var $scope, $rootScope, $httpBackend, createController;
beforeEach(module('angularMoon'));
beforeEach(inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
$rootScope = $injector.get('$rootScope');
$scope = $rootScope.$new();
var $controller = $injector.get('$controller');
createController = function() {
return $controller('SourceController', {
'$scope': $scope
});
};
}));
it("should set the current menu item to 'source'", function() {
createController();
$scope.init();
expect($rootScope.currentItem).toBe('source');
});
it("should get the contents of the root folder", function() {
createController();
$scope.init();
// NOT SURE WHAT TO DO HERE!
});
});
})();
I want to test that the fileService had it's getContents function called and mock a response so that I can test the two scenarios (if is array and if isn't`)
I would recommend using Jasmine spies for this.
Here is an example that might help. I usually put the spyOn call in the beforeEach.
var mockedResponse = {};
spyOn(fileService, "getContents").andReturn(mockedResponse);
In the 'it' part:
expect(fileService.getContents).toHaveBeenCalled();
To get the response, just call the method in your controller that calls the fileService method. You may need to manually run a digest cycle too. Snippet from one of my tests:
var testOrgs = [];
beforeEach(inject(function(coresvc) {
deferred.resolve(testOrgs);
spyOn(coresvc, 'getOrganizations').andReturn(deferred.promise);
scope.getAllOrganizations();
scope.$digest();
}));
it("getOrganizations() test the spy call", inject(function(coresvc) {
expect(coresvc.getOrganizations).toHaveBeenCalled();
}));
it("$scope.organizations should be populated", function() {
expect(scope.allOrganizations).toEqual(testOrgs);
expect(scope.allOrganizations.length).toEqual(0);
});
deferred in this case is a promise created with $q.defer();
You can create a spy and verify only that fileService.getContents is called, or either verify extra calls (like promise resolution) by making the spy call through. Probably you should also interact with httpBackend since you may need to flush the http service (even though you use the mock service).
(function() {
describe('SourceController', function() {
var $scope, $rootScope, $httpBackend, createController, fileService;
beforeEach(module('angularMoon'));
beforeEach(inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
$rootScope = $injector.get('$rootScope');
$scope = $rootScope.$new();
// See here
fileService = $injector.get('fileService');
spyOn(fileService, 'getContents').andCallThrough();
var $controller = $injector.get('$controller');
createController = function() {
return $controller('SourceController', {
'$scope': $scope
'fileService': fileService
});
};
}));
it("should get the contents of the root folder", function() {
createController();
$scope.init();
expect(fileService.getContents).toHaveBeenCalled();
});
});
})();
You can also add expectations to what happens inside the callback but you should issue a httpBackend.flush() before.
I am using v1.2.0-rc.3 of AngularJS with Jasmine test framework.
I am trying to assert that a controller calls a service method. The service method returns a promise. The controller looks like this:
angular.module('test', [])
.controller('ctrl', ['$scope', 'svc', function ($scope, svc) {
$scope.data = [];
svc.query()
.then(function (data) {
$scope.data = data;
});
}]);
I want to test that data is assigned to the scope when the service method's deferred is resolved. I have created a mock for the service and the unit test looks like this:
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;
}
};
scope = $rootScope.$new();
controller = $controller('ctrl', {
$scope: scope,
svc: svc
});
}));
it('should assign data to scope', function () {
spyOn(svc, 'query').andCallThrough();
deferred.resolve(data);
scope.$digest();
expect(svc.query).toHaveBeenCalled();
expect(scope.data).toBe(data);
});
});
I expect the query method of svc to be called, but apparently it hasn't.
I followed this guide to mocking promises in unit tests.
What am I doing wrong?
It seems I was placing my spy in the wrong place. When I place it in the beforeEach, the test passes.
beforeEach(inject(function($controller, $rootScope, $q) {
svc = {
query: function () {
def = $q.defer();
return def.promise;
}
};
spyOn(svc, 'query').andCallThrough();
scope = $rootScope.$new();
controller = $controller('ctrl', {
$scope: scope,
svc: svc
});
}));
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;
}