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';
}
});
Related
I am trying to unit test my controller. The function that I am trying to unit test is:
function myFunction() {
MyService
.myMethod(thing1, thing2)
.then(function handleMyMethod(result) {
SomeModule.errorHandler(result)
.onSuccess(function onSuccess() {
// do stuff
})
.onError(function onError() {
// do stuff
});
});
}
Relevant test file snippet:
var MockService = {
myMethod: function(thing1, thing2) {
var promise = $q.defer().promise;
return promise;
}
};
beforeEach(module(function($provide) {
$provide.value('MyService', MockService);
}));
beforeEach(inject(function (_$controller_, _MyService_, _SomeModule_, ...) {
...
MyService = _MyService_;
MyController = _$controller_('MyController as Ctrl', {
$controller: controller,
MyService: MockService,
});
I am confused about how to write tests that allow me to hit both the onSuccess and onError cases. I am trying to cover both branches for branch coverage, but don't know how the syntax works.
You can do it one of two ways:
You can write your mock service to look at the parameters and resolve with an error or success.
myMethod:function(thing1, thing2) {
if (thing1=='all good') return $q.when('excellent');
return $q.reject('sorry, bud');
}
You can override the method closer to where you're calling it.
it('is a success', function() {
spyOn(MockService, 'myMethod').and.returnValue($q.when('excellent');
$rootScope.$apply(
MyController.doStuff('foo');
);
expect(MyController.someProperty).toEqual('excellent');
});
//etc.
Note you don't need to both override the module injector with the provide code and provide the mock service in the $controller locals parameter.
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.
I'm trying to create a provider to handle authentication in my app
function Authenticator($http) {
console.log($http);
return {
test: function() {}
}
}
app.provider('authenticator', function AuthenticatorProvider() {
this.config = function () {
var requestParams;
return {
setRequestParams: function (params) {
requestParams = params;
}
}
}();
this.$get = function($http) {
return new Authenticator($http);
};
});
When i run the code above $http is set as undefined. What am i doing wrong? What's the right way to inject $http service into a custom provider?
Thanks
I am guessing what you really want to be doing is something like this:
app.factory('AuthenticationService', ['$http', function($http) {
var AuthenticationService = function() {};
AuthenticationService.prototype.config = function)() { ... }
return new AuthenticationService();
}]);
This creates a service that can be injected into other controllers, directives and services, which there will only ever be a single shared instance of. Fetching the service by its string means the reference inside the function is local to the closure, which means the variable can be safely renamed, saving precious bandwidth.
I have a controller that immediately grabs some data from a service when it loads.
angular.module('app').controller("MyController", function (myService) {
var projectId = myservice.project.id;
});
This data gets set from a previous action in the application. So when karma/jasmine loads this controller in a fresh state this service doesn't have this data. I've tried mocking this service in a beforeEach block but haven't had any luck.
beforeEach(function () {
var myService = {
project: {
id: 'thadsfkasj'
}
}
});
What am I missing?
You can mock your service using angular.value. This should allow you to then inject myService into your controller.
angular.module('myServiceMock', [])
.value('myService',
{
project: {
id: 'thadsfkasj'
}
});
beforeEach(module(
'myServiceMock'
//other modules or mocks to include...
));
Are you using ngMock as well?
If so, you should do the following:
beforeEach(module(function ($provide) {
var myService = {
project: {
id: 'thadsfkasj'
}
}
$provide.value('myService', myService);
}));
Test AngularJS factory function with Jasmine
Without having to deal with injecting $provide or overriding modules, you can simply pass in an object representing your mock when you instantiate your controller in your tests.
beforeEach(inject(function (_$controller_) {
myMockService = {
project: {
id: 100
}
};
ctrl = _$controller_('MyController', {
...
myService: myMockService
...
});
}));
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.