Jasmine and AngularJS - Unit testing a mocked dependency - angularjs

I have an AuthService that has a dependency on userService which comes from a different module
I'm trying to write a unit test that checks a property of the userService which I have mocked out and included using the $provide service in my test spec.
beforeEach(function() {
module('app.services');
//Need to provide userService since it lives in a different module
module(function ($provide) {
$provide.value('userService', function(){
return {
userLoggedIn: false
}
});
});
});
//Question - will injecting _userService_ pick up the mocked instance from above?
beforeEach(inject(function(_AuthService_, _userService_) {
authService = _AuthService_;
userService = _userService_;
}));
describe('some text', function() {
it('should make a get request to check the users token', function() {
$httpBackend.expectGET('/auth/checktoken').respond({});
authService.hasAccessToken();
$httpBackend.flush();
});
it('should set userService.userLoggedIn to be true', function() {
expect(userService.userLoggedIn).toBeTruthy();
});
});
My expectation on my userService fails since it comes back as undefined. What do I need to do in order to test my mocked out userService?
Thanks
UPDATE
Ok so based on the comments, I can get it to pass by organising my tests like so
describe('hasAccessToken', function() {
beforeEach(function(){
authService.hasAccessToken();
});
it('should make a get request to catalogues', function() {
$httpBackend.expectGET('/auth/checktoken').respond({});
$httpBackend.flush();
});
it('should set userService.userLoggedIn to be true', function() {
$httpBackend.expectGET('/auth/checktoken').respond({});
$httpBackend.flush();
expect(userService.userLoggedIn).toBeTruthy();
});
});
As you can see I'm trying to write a test for the expectGET and the userService separately, is this correct? Just seems quite verbose??

You are using the angular's value synthetic sugar for provider, but you supply a constructor, which should be used with the service syntax.
try to use the service constructor with the service syntax instaed:
beforeEach(module(function ($provide) {
$provide.service('userService', function(){
return {
userLoggedIn: false
}
});
});
or just use the service's instance with the value syntax:
beforeEach(module(function ($provide) {
$provide.value('userService', {
userLoggedIn: false
}
});
});
see angular docs: $provide

Related

Angular Karma/Jasmine testing: How to mock a service in controller with promises?

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.

Angular Mocking in Jasmine Unknown provider

I can't get these two spec files to play well with each other. I didn't think spec files would effect other spec files but in this case it seem like they do, it makes no sense to me.
I'm using Jasmine and Karma the tests are automated with Gulp
The error I'm getting is "Unknown provider: ProductServiceProvider <- ProductService"
I have changed the tests to troubleshoot the issue here is the simple versions.
If I comment out the following line in file 2 both files pass.
angular.module('eu.product.service', []);
It has something to do with mocking the module but I can't figure out what I'm doing wrong here.
spec file 1
describe('Testing euProduct', function(){
var $factory;
var $httpBackend;
beforeEach(function () {
//modules
module('eu.product.service');
//injections
inject(function($injector){
$factory = $injector.get('ProductService');
$httpBackend = $injector.get('$httpBackend');
});
//mock data
$httpBackend.when('GET', '/Mercury/product/list/0/0?PrimaryCategoryID=0&pageSize=20&startPage=1').respond({
"data":
[{
"recallid":45,
}]
});
});
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
//-----Tests----
it('Should be able to get data from the server on default parameters.', function(){
$factory.list({},function(data){
expect(data.data[0].recallid).toBe(45);
});
$httpBackend.flush();
});
});
Spec file 2
'use strict';
describe('Testing euProduct Logic', function(){
//variables in closure scope so they can be used in tested but set with injection in beforeEach
var $factory;
//mocking a module :: http://www.sitepoint.com/mocking-dependencies-angularjs-tests/
beforeEach(function () {
angular.module('eu.product.service',[]);
module(function($provide) {
$provide.factory('ProductService', function() {
// Mocking utilSvc
return {
list : function(para, callback){
callback({
data : {
product : 'The product Name'
}
})
}
};
});
$provide.service('storageSvc', function() {
// Mocking storageSvc
});
});
//modules
module('eu.product.logic');
//injections
inject(function($injector){
$factory = $injector.get('ProductLogic');
});
});
//-----Tests----
it('Should be able to run tests', function(){
expect(2).toBe(2);
});
});
Both module and inject from angular-mocks return functions which need to be called.
In the following example I made these changes:
Refactor to a basic working example
Don't define custom $-prefixed variables. These are reserved by angular.
Use inject to inject instead of $injector.
Add some comments for further explanation.
describe('ProductService', function() {
var ProductService;
var $httpBackend;
// Start module config phase.
beforeEach(module('eu.produce.service', function($provide) {
// Inject providers / override constants here.
// If this function is empty, it may be left out.
}))
// Kickstart the app and inject services.
beforeEach(inject(function(_ProductService_, _$httpBackend_){
ProductService = _ProductService_;
$httpBackend = _$httpBackend_;
});
beforeEach(function() {
// Optionally use another beforeEach block to setup services, register spies, etc.
// This can be moved inside of the inject function as well if you prefer.
//mock data
$httpBackend.when('GET', '/Mercury/product/list/0/0?PrimaryCategoryID=0&pageSize=20&startPage=1').respond({
"data":
[{
"recallid":45,
}]
});
});
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
//-----Tests----
it('Should be able to get data from the server on default parameters.', function(){
ProductService.list({},function(data){
expect(data.data[0].recallid).toBe(45);
});
$httpBackend.flush();
});
});

Calling a method from an injected service in Jasmine

I'm attempted to unit test a service. I've injected the service however the method call getAllProducts() doesn't appear to run however the test still passes!
Plnkr
service.js
angular.module('vsApp')
.factory('productsDataService', function($http) {
var service = {
getAllProducts: getAllProducts
};
// get all products
function getAllProducts() {
return $http.get('/apiv1/getAllProducts/').then(function(data) {
return (data);
});
}
return service;
});
spec.js
// jasmine
describe('products data service', function () {
var $httpBackend, productsDataService;
beforeEach(module('vsApp'));
beforeEach(inject(function(_$httpBackend_, _productsDataService_) {
$httpBackend = _$httpBackend_;
productsDataService = _productsDataService_;
}));
it('should get all products', inject(function() {
console.info("get all");
// mock response for the http call in the service
$httpBackend.when('GET', '/apiv1/getAllProducts/')
.respond({name: 'item', price: '932'});
//this doesn't seem to run??
productsDataService.getAllProducts().then(function(response) {
expect(response.data.length).toBeGreaterThan(1);
});
}));
});
Ok, you have to make it sync. (all pending request will get resolved) using $http.flush();
Working demo as expected
productsDataService.getAllProducts().then(function(response) {
console.log(response);
expect(response.data.length).toBeGreaterThan(999);
});
$httpBackend.flush(); // <=============== here.

Testing angular controller initialisation with different conditions

I have a controller that takes a dependency on a service, and as part of it's initialisation calls a function on the service. Here's a contrived example:
describe('tests', function() {
var _scope, service, serviceValue = 'value';
beforeEach(module('app'));
beforeEach(inject(['$rootScope','$controller', function($rootScope, $controller) {
_scope = $rootScope.$new();
service = {
get: function(key) {
return serviceValue;
}
};
$controller('myController', {
'$scope': _scope,
'service': service
});
}]));
describe('initialisation', function() {
describe('key exists', function() {
it('should find the key', function() {
expect(_scope.message).toBe('found the key');
});
});
describe('key does not exist', function() {
beforeEach(function() {
serviceValue = undefined;
});
it('should not find the key', function() {
expect(_scope.message).toBe('did not find the key');
});
});
});
});
angular.module('app').controller('myController', ['$scope','service',
function($scope, service) {
if(service.get('key') === 'value') {
$scope.message = 'found the key';
} else {
$scope.message = 'did not find the key';
}
});
The tests for when the key does not exist fail because the controller initialisation has run in the first beforeEach, before the next beforeEach runs to change the service return value.
I can get around this by recreating the whole controller in the beforeEach of the 'key does not exist' tests, but this seems wrong to me, as it initialises the controller twice for the test. Is there a way to get the controller initialisation to run for every test, but after all other beforeEach functions have run.
Is this the right way to be initialising controllers? Am I missing some feature of jasmine?
Creating the controller for each test is the recommended way, especially when you have initialization logic.
I would however use Jasmine's spyOn to set up what the service returns and tracking calls to it, instead of modifying internal values of a mocked or real service.
Inject the real service and save it in a variable, and define a function that creates the controller:
describe('tests', function() {
var $scope, createController, service;
beforeEach(function() {
module('app');
inject(function($rootScope, $controller, _service_) {
$scope = $rootScope.$new();
service = _service_;
createController = function() {
$controller('myController', {
'$scope': $scope,
'service': service
});
};
});
});
For each test use spyOn to intercept calls to the service and decide what it should return, then create the controller:
describe('initialisation', function() {
it('should find the key', function() {
spyOn(service, 'get').and.returnValue('value');
createController();
expect($scope.message).toBe('found the key');
});
it('should not find the key', function() {
spyOn(service, 'get').and.returnValue(undefined);
createController();
expect($scope.message).toBe('did not find the key');
});
});
Demo: http://plnkr.co/edit/BMniTis1RbOR0h5O4kZi?p=preview
As spyOn sets up tracking you can now for example also make sure the service only gets called once on controller initilization:
spyOn(service, 'get').and.returnValue('value');
expect(service.get.calls.count()).toEqual(0);
createController();
expect(service.get.calls.count()).toEqual(1);
Note: The examples above use Jasmine 2.0. Syntaxes will have to be slightly modified for older versions.

How can you test an angualar service while mocking a dependency of that service?

So I have two services:
// The service I'm testing
angular.module("m").service("myService", function(otherService) { ... })
// the service I'd like to mock while testing
angular.module("m").service("otherService", function() { ... })
describe("my test", function() {
var myService = null;
beforeEach(module('m'));
beforeEach(inject(function($injector) {
///////////////////////////////////////////////////////
// but I want it to get injected with 'otherService'
///////////////////////////////////////////////////////
myService = $injector.get("myService")
})
it ('test myService', function() {
})
})
I want to mock out otherService before it's injected into myService and I test the instance of myService in follow up it functions.
You should use the $provide service to replace the otherService implementation with a mocked one. Here you go:
describe('my test', function() {
var myService, otherServiceMock;
beforeEach(function() {
module('m');
otherServiceMock = jasmine.createSpyObj('otherService', [...]);
module(function($provide) {
// Replaces the service with a mock object
$provide.value('otherService', otherServiceMock);
});
inject(function(_myService_) {
myService = _myService_;
});
});
});
Check out the $provide documentation for more information.
You can just mock the methods of the service in question on the fly
var myService, otherService;
beforeEach(inject(function($injector) {
myService = $injector.get('myService');
otherService = $injector.get('otherService');
}));
it('calls otherService.doOther when doSomething is called', function() {
spyOn(otherService, 'doOther');
myService.doSomething();
expect(otherService.doOther).toHaveBeenCalled();
});
With the jasmine spies, you can for example test outcomes with different return values, etc.
it('doesSomething returns true when otherService.doOther returns false', function() {
spyOn(otherService, 'doOther').andReturn(false);
expect(myService.doSomething()).toBeTruthy();
});

Resources