In my main describe I have the following:
beforeEach(inject(function(...) {
var mockCookieService = {
_cookies: {},
get: function(key) {
return this._cookies[key];
},
put: function(key, value) {
this._cookies[key] = value;
}
}
cookieService = mockCookieService;
mainCtrl = $controller('MainCtrl', {
...
$cookieStore: cookieService
}
}
Later on I want to test how a controller believes if the cookie already exists, so I nest the following describe:
describe('If the cookie already exists', function() {
beforeEach(function() {
cookieService.put('myUUID', 'TEST');
});
it('Should do not retrieve UUID from server', function() {
expect(userService.getNewUUID).not.toHaveBeenCalled();
});
});
However when I'm making the change to cookieService it's not persisting into the controller being created. Am I taking the wrong approach?
Thanks!
EDIT: Updated the testing code and this is how I'm using $cookieStore:
var app = angular.module('MyApp', ['UserService', 'ngCookies']);
app.controller('MainCtrl', function ($scope, UserService, $cookieStore) {
var uuid = $cookieStore.get('myUUID');
if (typeof uuid == 'undefined') {
UserService.getNewUUID().$then(function(response) {
uuid = response.data.uuid;
$cookieStore.put('myUUID', uuid);
});
}
});
Your unit tests need not have to create a mock $cookieStore and essentially re-implement its functionality. You can use Jasmine's spyOn function to create a spy object and return values.
Create a stub object
var cookieStoreStub = {};
Set up your spy object before creating the controller
spyOn(cookieStoreStub, 'get').and.returnValue('TEST'); //Valid syntax in Jasmine 2.0+. 1.3 uses andReturnValue()
mainCtrl = $controller('MainCtrl', {
...
$cookieStore: cookieStoreStub
}
Write unit tests for the scenario in which cookie is available
describe('If the cookie already exists', function() {
it('Should not retrieve UUID from server', function() {
console.log(cookieStore.get('myUUID')); //Returns TEST, regardless of 'key'
expect(userService.getNewUUID).not.toHaveBeenCalled();
});
});
Note: If you'd like to test multiple cookieStore.get() scenarios, you might want to move the creation of the controller into a beforeEach() inside the describe() block. This allows you to call spyOn() and return a value appropriate for the describe block.
Related
After a week looking for a good answer/sample, I decided to post my question.
I need to know how is the best way to code and test something like this:
Controller
// my.controller.js
(function () {
'use strict';
angular.module('myApp.myModule').controller('Awesome', Awesome);
function Awesome($http, $state, AwesomeService) {
var vm = this; // using 'controllerAs' style
vm.init = init;
vm.awesomeThingToDo = awesomeThingToDo;
vm.init();
function awesomeThingToDo() {
AwesomeService.awesomeThingToDo().then(function (data) {
vm.awesomeMessage = data.awesomeMessage;
});
}
function init() {
vm.awesomeThingToDo(); // Should be ready on page start
}
}
})();
Service
// my.service.js
(function () {
'use strict';
angular.module('myApp.myModule').factory('AwesomeService', AwesomeService);
function AwesomeService($resource, $http) {
var service = {
awesomeThingToDo: awesomeThingToDo
}
return service;
function awesomeThingToDo() {
var promise = $http.get("/my-backend/api/awesome").then(function (response) {
return response.data;
});
return promise;
}
}
})();
My app works OK with this structure. And my Service unit tests are OK too.
But I don't know how to do unit tests on Controller.
I tried something like this:
Specs
// my.controller.spec.js
(function () {
'use strict';
describe("Awesome Controller Tests", function() {
beforeEach(module('myApp.myModule'));
var vm, awesomeServiceMock;
beforeEach(function () {
awesomeServiceMock = { Is this a good (or the best) way to mock the service?
awesomeThingToDo: function() {
return {
then: function() {}
}
}
};
});
beforeEach(inject(function ($controller) {
vm = $controller('Awesome', {AwesomeService : awesomeServiceMock});
}));
it("Should return an awesome message", function () {
// I don't know another way do to it... :(
spyOn(awesomeServiceMock, "awesomeThingToDo").and.callFake(function() {
return {
then: function() {
vm.awesomeMessage = 'It is awesome!'; // <-- I think I shouldn't do this.
}
}
});
vm.awesomeThingToDo(); // Call to real controller method which should call the mock service method.
expect(vm.awesomeMessage).toEqual('It is awesome!'); // It works. But ONLY because I wrote the vm.awesomeMessage above.
});
});
})();
My app uses Angular 1.2.28 and Jasmine 2.1.3 (with Grunt and Karma).
UPDATE: Solved!
it("Should return an awesome message", function () {
// Solved with callback parameter
spyOn(awesomeServiceMock, "awesomeThingToDo").and.callFake(function(callback) {
return {
then: function(callback) {
callback({awesomeMessage: 'It is awesome!'}); //callback call works fine! :D
}
}
});
I updated the question with a possible (bad) solution:
it("Should return an awesome message", function () {
// Solved with callback parameter
spyOn(awesomeServiceMock, "awesomeThingToDo").and.callFake(function(callback) {
return {
then: function(callback) {
callback({awesomeMessage: 'It is awesome!'}); //callback call works fine! :D
}
}
});
I used a callback to pass the mocked parameter and call the real implementation. :D
No, that's not how I would do this.
First, there is no need to create a mock service: you can inject the real one, and spy on it.
Second, Angular has everything you need to create promises and to resolve them. No need to create fake objects with a fake then() function.
Here's how I would do it:
describe("Awesome Controller Tests", function() {
beforeEach(module('myApp.myModule'));
var vm, awesomeService, $q, $rootScope;
beforeEach(inject(function($controller, _awesomeService_, _$q_, _$rootScope_) {
$q = _$q_;
awesomeService = _awesomeService_;
$rootScope = _$rootScope_;
vm = $controller('Awesome');
}));
it("Should return an awesome message", function () {
spyOn(awesomeService, "awesomeThingToDo").and.returnValue(
$q.when({
awesomeMessage: 'awesome message'
}));
vm.awesomeThingToDo();
// at this time, the then() callback hasn't been called yet:
// it's called at the next digest loop, that we will trigger
$rootScope.$apply();
// now the then() callback should have been called and initialized
// the message in the controller with the message of the promise
// returned by the service
expect(vm.awesomeMessage).toBe('awesome message');
});
});
Unrelated note: 1.2.28 is quite old. You should migrate to the latest version.
How can I test the value of a promise, returned by a service? In the $q documentation, the promise value is preset in the test using resolve(value). Other approaches test the service logic in a controller, using the fact that AngularJS evaluates the promises and binds the values the $scope.
In my opinion, none of these approaches actually test the logic of the service in the place where it should be tested. How can I test that the resolved promise (which is returned by the service) contains the correct value?
Here an example:
myApp.service('service', function($q){
var obj = {};
obj.test = function() {
var deferred = $q.defer();
deferred.resolve(false);
return deferred.promise;
}
return obj;
});
In order to test the service, I want to do the following in theory (which does not work in practice):
var $q, service;
beforeEach(function () {
module('myModule');
service = $injector.get('service');
});
describe('...', function() {
it('Testing whether promise contains correct value', function() {
var myPromise = service.test();
myPromise.then(function(value) {
expect(value).toBeFalsy();
});
});
});
I believe you are injecting the service in a wrong way. You have to use the underscore notation. Please refer this link. http://nathanleclaire.com/blog/2014/04/12/unit-testing-services-in-angularjs-for-fun-and-for-profit/
So, your test should look something like this.
var service;
beforeEach(function () {
module('myModule');
inject(function(_service_) {
service = _service_;
});
});
describe('...', function() {
it('Testing whether promise contains correct value', function() {
var myPromise = service.test();
myPromise.then(function(value) {
expect(value).toBeFalsy();
});
});
});
I'm using a service to share data between controllers. If a value on the service changes, I want to update some data binding on my controllers. To do this, I'm using $scope.$watchCollection (because the value I'm watching is a simple array). I'm having trouble trying to figure out how to test this in Jasmine + Karma.
Here is a simple Controller + Service setup similar to what I'm doing in my app (but very simplified):
var app = angular.module('myApp', []);
// A Controller that depends on 'someService'
app.controller('MainCtrl', function($scope, someService) {
$scope.hasStuff = false;
// Watch someService.someValues for changes and do stuff.
$scope.$watchCollection(function(){
return someService.someValues;
}, function (){
if(someService.someValues.length > 0){
$scope.hasStuff = false;
} else {
$scope.hasStuff = true;
}
});
});
// A simple service potentially used in many controllers
app.factory('someService', function ($timeout, $q){
return {
someValues: []
};
});
And here is a test case that I've attempted (but does not work):
describe('Testing a controller and service', function() {
var $scope, ctrl;
var mockSomeService = {
someValues : []
};
beforeEach(function (){
module('myApp');
inject(function($rootScope, $controller) {
$scope = $rootScope.$new();
ctrl = $controller('MainCtrl', {
$scope: $scope,
someService: mockSomeService
});
});
});
it('should update hasStuff when someService.someValues is changed', function (){
expect($scope.hasStuff).toEqual(false);
// Add an item to someService.someValues
someService.someValues.push(1);
//$apply the change to trigger the $watch.
$scope.$apply();
//assert
expect($scope.hasStuff).toEqual(true);
});
});
I guess my question is twofold:
How do I properly mock the service that is used in the controller?
How do I then test that the $watchCollection function is working properly?
Here is a plunkr for the above code. http://plnkr.co/edit/C1O2iO
Your test (or your code ) is not correct .
http://plnkr.co/edit/uhSdk6hvcHI2cWKBgj1y?p=preview
mockSomeService.someValues.push(1); // instead of someService.someValues.push(1);
and
if(someService.someValues.length > 0){
$scope.hasStuff = true;
} else {
$scope.hasStuff = false;
}
or your expectation makes no sense
I strongly encourage you to lint your javascript (jslint/eslint/jshint) to spot stupid errors like the first one.Or you'll have a painfull experience in writing javascript. jslint would have detected that the variable you were using didnt exist in the scope.
I am attempting to unit test my individual Angular factories but am having a hard time trying to correctly mock and inject the PouchDB object. My factory code is currently as follows:
factory('Track', [function() {
var db = new PouchDB('tracks');
var resource = {
getAll: function() {
return db.allDocs({include_docs: true});
}
return resource;
}]);
I had tried to use Angular's $provide service to inject a mock PouchDB instance with no luck:
module(function($provide) {
$provide.value('PouchDB', {
allDocs: function() {
return 'MOCKED';
}
});
I am not entirely sure where to go from here. Any help would be greatly appreciated!
As just stated in the comments:
you have to wrap the global variable PouchDB inside a service to make it injectable. This is due to Angular doing DI via simple function-parameters. So just do something like:
angular.module('myModule')
.factory('PouchDBWrapper', function(){
return PouchDB;
}
Then you can inject it into your Track factory:
factory('Track', [function(PouchDBWrapper) {
var db = new PouchDBWrapper('tracks');
var resource = {
getAll: function() {
return db.allDocs({include_docs: true});
}
return resource;
}]);
and in your test you can mock it by:
module(function($provide) {
$provide.factory('PouchDBWrapper', {
allDocs: function() {
return 'MOCKED';
}
});
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.