I have created an Angular factory that has methods which handle saving code to a server. One of the factory methods contains a third party object which has a method which does the actual callout. I would like to test this code, but I can't work out how to mock out the third party object.
I have set up a plunker with a Jasmine test.
My aim for this test is just to successfully get the code to use my mock object rather than the ThirdPartySavingUtils object. Is that possible?
var app = angular.module("MyApp", []);
app.factory("SavingUtils", function() {
return {
saveStuff: function() {
if(typeof ThirdPartySavingUtils !== "undefined") {
return ThirdPartySavingUtils.value;
}
}
};
});
this is my jasmine tests
describe("Mocking Test", function() {
var ThirdPartySavingUtilsMock;
var SavingUtils;
beforeEach(function() {
angular.mock.module("MyApp", function($provide) {
ThirdPartySavingUtilsMock = {
value: "I am the mock object"
};
$provide.value("ThirdPartySavingUtils", ThirdPartySavingUtilsMock);
});
inject(function(_SavingUtils_) {
SavingUtils = _SavingUtils_;
});
});
it("should run without throwing an exception", function() {
expect(true).toBe(true);
});
it("should mock out ThirdPartySavingUtils with ThirdPartySavingUtilsMock", function() {
var result = SavingUtils.saveStuff();
expect(result).toEqual("I am the mock object");
});
});
You have a few options really but more than likely you would need to do both.
1) You could create an angular service which wraps this third party object - this way you get a nice abstraction incase you ever need to change the third party object.
2) You could use a mocking framework like http://sinonjs.org/ which enable you to mock methods out and do asserts like calledOnce etc.
Here is a link to a mocked test using sinon test.
You can bascially see sinon is used as a sandbox to mock out an object methods. Sinon provides extra propeties to those mocked methods so you can assert if they were called, the parameters they were called with even the order of the calls. It is a really, really essential testing tool.
describe('validationManager', function () {
beforeEach(inject(function ($injector) {
sandbox = sinon.sandbox.create();
$rootScope = $injector.get('$rootScope');
$compile = $injector.get('$compile');
$q = $injector.get('$q');
defer = $q.defer();
validator = $injector.get('validator');
validationManager = $injector.get('validationManager');
sandbox.stub(validator, 'makeValid');
sandbox.stub(validator, 'makeInvalid');
sandbox.stub(validator, 'getErrorMessage').returns(defer.promise);
setModelCtrl();
}));
afterEach(function () {
sandbox.restore();
setModelCtrl();
});
it('should be defined', function () {
expect(validationManager).to.exist;
});
describe('validateElement', function () {
it('should return if no $parsers or $formatters on the controller', function () {
validationManager.validateElement(modelCtrl);
expect(validator.makeValid.called).to.equal(false);
expect(validator.makeInvalid.called).to.equal(false);
});
});
EDIT -----------------------
Here this put into practice for your code (I haven't run this but it give the general idea).
(function (angular, ThirdPartyApi) {
'use strict';
var app = angular.module('MyApp', []);
app.factory('thirdPartApi', [
function () {
return {
save: ThirdPartyApi.save,
value: ThirdPartyApi.value
};
}
]);
app.factory('SavingUtils', [
'thirdPartApi',
function (thirdPartApi) {
var getValue = function () {
return thirdPartApi.value;
},
save = function (item) {
return thirdPartApi.save(item);
};
return {
save: save,
getValue: getValue
};
}
]);
}(angular, window.ThirdPartyApi));
The tests.....
(function (angular, sinon) {
'use strict';
describe('MyApp.SavingUtils', function () {
var sandbox, thirdPartyApi, SavingUtils, thirdPartyApiValue = 2;
beforeEach(inject(function ($injector) {
sandbox = sinon.sandbox.create();
thirdPartyApi = $injector.get('thirdPartyApi');
SavingUtils = $injector.get('SavingUtils');
// stub the method and when called return a simple object or whatever you want
sandbox.stub(thirdPartyApi, 'save').returns({ id: 1});
sandbox.stub(thirdPartyApi, 'value', function () {
return thirdPartyApiValue;
});
}));
afterEach(function () {
// This removes those stubs and replace the original methods/values
sandbox.restore();
});
describe('save', function () {
it('should return call the save method on thirdPartyApi', function () {
var item = {};
SavingUtils.save(item);
expect(thirdPartyApi.save.calledOnce).to.equal(true);
});
});
describe('getValue', function () {
it('should return value of value property on thirdPartyApi', function () {
var result = SavingUtils.getValue();
expect(result).to.equal(thirdPartyApiValue);
});
});
});
}(angular, sinon));
Related
I have a webworker doing some work for me.
I've wrapped this into a service and this webworker is executed in a Promise.
Now I'm tesing this with Jasmine, and it seems that the promised is returned after the test has finished.
The difficulty in here is that the defer AND the webworker are both async at different points in time.
I've tried with async jasmine with done, setTimeout, $scope.$apply(). But ''deferred.resolve(e.data.filtered)'' is called after all those timers have suspended.
My angular service is like this:
'use strict';
angular.module('app.demographics').service('FilteringService', FilteringService);
FilteringService.$inject = ['$q'];
function FilteringService($q) {
this.filter = function (dataSet, filters) {
var deferred = $q.defer();
var worker = new Worker('my.worker.js');
var filterData = {
dataSet: dataSet,
filters: filters
};
worker.postMessage(filterData);
worker.onmessage = function (e) {
if (e.data && e.data.ready) {
deferred.resolve(e.data.filtered);
}
};
worker.onerror = function (e) {
console.log("something went wrong while filtering: ", e);
deferred.reject(e);
};
return deferred.promise;
};
}
And my test is like this, which I expect to work properly, but it never comes to the expect.
'use strict';
describe('FilteringService: ', function () {
var filteringService, $q,
dataSet = [{a: 1, b: 2}, {c: 3, d: 4}],
filters = [];
beforeEach(function () {
module('app.demographics');
inject(function (_$rootScope_, _FilteringService_, _$q_) {
filteringService = _FilteringService_;
$q = _$q_;
});
});
it('should return a promise on filtering', function () {
var filteringPromise = filteringService.filter(dataSet, filters);
filteringPromise.then(function (data) {
expect(data.length).toEqual(dataSet.length);
}, function (failure) {
fail(failure);
});
});
});
As mentioned in https://stackoverflow.com/a/37853075/1319998, the original test seems to be more of an integration test rather than a unit test. If you would like this to be a unit test....
You need to be able to mock the worker so you're not testing what it does. So in the service, instead of calling Worker directly, you can call $window.Worker, since $window can be easily mocked in tests.
app.service('FilteringService', FilteringService);
FilteringService.$inject = ['$window', '$q', '$rootScope'];
function FilteringService($window, $q, $rootScope) {
this.filter = function (dataSet, filters) {
var deferred = $q.defer();
var worker = new $window.Worker('my.worker.js');
...
Then in the test you can create a mocked worker, calling the attacted onmessage handler that would be called by the real worker, and testing that the promise then gets resolved with the correct value (I've left it as just testing the length, but in a real test I suspect you will need something a bit better).
describe('FilteringService: ', function () {
var $rootScope, filteringService, $q,
dataSet = [{a: 1, b: 2}, {c: 3, d: 4}],
filters = [];
var mockWorker;
var mockWindow = {
Worker: function() {
return mockWorker;
}
};
beforeEach(function () {
module('app.demographics');
module(function($provide) {
$provide.value('$window', mockWindow);
});
inject(function (_$rootScope_, _FilteringService_, _$q_) {
$rootScope = _$rootScope_;
filteringService = _FilteringService_;
$q = _$q_;
});
mockWorker = {
postMessage: jasmine.createSpy('onMessage')
}
});
it('when onmessage from worker called, resolves returned promise with filtered list', function () {
expect(mockWorker.postMessage).not.toHaveBeenCalled();
expect(mockWorker.onmessage).not.toEqual(jasmine.any(Function));
var filteringPromise = filteringService.filter(dataSet, filters);
expect(mockWorker.postMessage).toHaveBeenCalled();
expect(mockWorker.onmessage).toEqual(jasmine.any(Function));
mockWorker.onmessage({
data: {
ready: true,
filtered: dataSet
}
});
var result;
filteringPromise.then(function(_result) {
result = _result;
});
$rootScope.$apply();
expect(result.length).toEqual(dataSet.length);
});
});
Note you then need the $apply in the test (but not the service), to make sure the promise callbacks get called.
You can see this working at https://plnkr.co/edit/g2q3ZnD8AGZCkgkkEkdj?p=preview
I accept this isn't the optimal solution, more of a hack probably, but this is how I got Jasmine working with Angular. My approach was to create a function digestIt that takes the done function provided by Jasmine and invokes $digest using setInterval and returns a cleanup function.
function digestIt($rootScope, done) {
var intervalId: number,
_done = function() {
if (angular.isDefined(intervalId))
clearInterval(intervalId);
intervalId = null;
done();
},
_interval = function () {
if (angular.isNumber(intervalId)) {
try {
$rootScope.$digest();
} catch (e) {
_done();
}
}
},
intervalId = setInterval(_interval, 1);
return _done;
}
Here's the usage pattern.
describe("MyService ", function() {
var $rootScope,
$injector
;
beforeEach(angular.mock.inject(function (_$rootScope_, _$injector_) {
$rootScope = _$rootScope_.$new();
$injector = _$injector_;
}));
it("My Test", function (done) {
var $docs = $injector.get('MyService'),
completed = digestIt($rootScope, done)
;
$docs.asyncCall().then(function () {
/* expect */
}).catch(function() {
/* fail */
}).finally(function () {
completed();
});
});
});
It looks likes (at least in the testing environment), $q promises only get resolved (as in, their success/failure callbacks are called) when a digest cycle gets initiated. So in the service you can put in $rootScope.apply() to trigger this:
worker.onmessage = function (e) {
if (e.data && e.data.ready) {
$rootScope.$apply(function() {
deferred.resolve(e.data.filtered);
});
}
};
worker.onerror = function (e) {
console.log("something went wrong while filtering: ", e);
$rootScope.$apply(function() {
deferred.reject(e);
});
};
And then your test can be asynchronous:
it('should return a promise on filtering', function (done) {
var filteringPromise = filteringService.filter(dataSet, filters);
filteringPromise.then(function (data) {
expect(data.length).toEqual(dataSet.length);
done();
}, function (failure) {
fail(failure);
});
});
This can be seen at https://plnkr.co/edit/D21EhoCXIbj8R0P9RY40?p=preview
Note: this is probably classified as an integration test rather than a unit test, as you're testing both FilteringService and your worker together. If you were to only have a unit test, you can probably avoid the addition of $rootScope.$apply() in FilteringService by mocking the worker. You would probably also then be able to make is a synchronous test.
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 to create Jasmine unit test for one function in AngularJS service provider. I want to create mock data for myObject and test function getObjectShape() with that mock data as parameter. How to achieve that?
(function () {
'use strict';
angular.module('objectShapes')
.provider('shapesResolver', shapesResolver);
function shapesResolver() {
this.$get = function () {
return resolver;
};
function resolver(myObject) {
var service = {
getObjectShape: getObjectShape
};
function getObjectShape() {
return myObject.Shape;
}
}
}
})();
Here's a skeleton of a test for your service.
describe('shapesResolver service', function() {
var shapesResolver;
beforeEach(module('objectShapes'));
beforeEach(inject(function(_shapesResolver_) {
shapesResolver = _shapesResolver_;
}));
it('should do something, but what?', function() {
var mockMyObject = {};
shapesResolver(mockMyObject);
// shapesResolver doesn't return anything, and doesn't
// have any side effect, so there's nothing to test.
expect(true).toBeTruthy();
});
});
We're unit testing our services and facing issue spying on methods with arguments of dependent services.
I am writing unit tests for ServiceA
ServiceA.js
angular.module("App").service("ServiceA", function($http, ServiceB) {
this.detail = null;
this.method = function(id){
var sevrB = new ServiceB();
return sevrB.getId(1).then(function(response) {
this.detail = response.data;
});
};
});
ServiceB.js (is a factory)
(function () {
var dependencies = [
'../module'
];
define(dependencies, function (module) {
return module.factory('ServiceB', function ($http) {
var ServiceB= function () {
this.id = null;
};
ServiceB.prototype.getId = function(Id) {
return $http.get('/test/');
}
}
}());
Unit test code
describe('Testing ServiceA', function () {
var serviceA, serviceBMock;
beforeEach(function () {
var _serviceBMock = function () {
return {
getId:function(id){
return 'test';
}
};
};
angular.module('ServiceAMocks', [])
.value('ServiceB', _serviceBMock);
});
beforeEach(module('ServiceAMocks'));
beforeEach(inject(function (_ServiceA_, _ServiceB_) {
serviceA=_ServiceA_;
serviceBMock=_ServiceB_;
});
it('retrive Id', function () {
spyOn(serviceBMock,'getId').and.Return('test');
serviceA.method(1);
});
});
I am spying on getId method of ServiceB from ServiceA and if i mocked ServiceB as function i am getting error below
Error: getId() method does not exist
at jasmineInterface.spyOn
If I mock serviceB as object then i get error as
TypeError: object is not a function
var _serviceBMock = {
getId:function(id){
return 'test';
}
}
And I am not sure of testing promise in this scenario.
This version supports Jasmine 1.3
I’m injecting $q service as ServiceB wants to call method then. We can even go forward and resolve returned promise, but this is next step in testing.
Answer to previous version of question, where AngularJS injects instance of serviceB
describe('ServiceA', function () {
var serviceA, ServiceB, $q;
beforeEach(function () {
module('App');
});
beforeEach(function () {
module(function ($provide) {
$provide.value('ServiceB', {
getId: jasmine.createSpy('ServiceB.getId').andCallFake(function () {
return $q.all();
})
});
});
});
beforeEach(inject(function (_ServiceA_, _ServiceB_, _$q_) {
$q = _$q_;
serviceA = _ServiceA_;
ServiceB = _ServiceB_;
}));
describe('.method()', function () {
it('returns ServiceB.getId() argument', function () {
serviceA.method(1);
expect(ServiceB.getId).toHaveBeenCalledWith(1);
});
});
});
jsfiddle: http://jsfiddle.net/krzysztof_safjanowski/sDh35/
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.