Given the app startup:
angular.module("starter", [ "ionic" ])
.constant("DEBUG", true)
.run(function() {
/* ... */
});
how would I test the value of DEBUG?
When trying with:
describe("app", function() {
beforeEach(function() {
module("starter");
});
describe("constants", function() {
describe("DEBUG", inject(function(DEBUG) {
it("should be a boolean", function() {
expect(typeof DEBUG).toBe("boolean");
});
}));
});
});
I just get
TypeError: 'null' is not an object (evaluating 'currentSpec.$modules')
at workFn (/%%%/www/lib/angular-mocks/angular-mocks.js:2230)
at /%%%/www/js/app_test.js:14
at /%%%/www/js/app_test.js:15
at /%%%/www/js/app_test.js:16
Make sure it is being instantiated in the right place.
In this case, the beforeEach was not being run to load the module, because DEBUG was being inject()ed in the describe block, not the it block. The following works properly:
describe("app", function() {
var DEBUG;
beforeEach(function() {
module("starter");
});
describe("constants", function() {
describe("DEBUG", function() {
it("should be a boolean", inject(function(DEBUG) {
expect(typeof DEBUG).toBe("boolean");
}));
});
});
});
Simple way to inject your existing constants into your karma tests.
// Assuming your constant already exists
angular.module('app').constant('STACK', 'overflow')...
// Your Karma Test Suite
describe('app', function() {
var STACK;
beforeEach(module('APP'));
beforeEach(inject(function ($injector) {
STACK = $injector.get('STACK');
}));
// Tests...
});
Related
I have a simple enough function that closes an $mdSidenav instance in my application
function closeSideNav() {
$mdSidenav('left').close();
}
I'm now needing to unit test this, but am having trouble writing an expectation for the close() call on $mdSidenav.
I thought about using $provide in my test spec
module(function($provide) {
$provide.value('$mdSidenav', function(id) {
return {
close: jasmine.createSpy('$mdSidenav.close')
}
})
});
beforeEach(inject(function(_$controller_, _$mdSidenav_) {
$controller = _$controller_;
$mdSidenav = _$mdSidenav_;
}));
beforeEach(function() {
vm = $controller('NavbarController', {
$mdSidenav: $mdSidenav
});
});
describe('vm.closeSideNav', function() {
beforeEach(function() {
spyOn($mdSidenav, 'close');
vm.closeSideNav()
});
it('should call $mdSidenav.close()', function() {
expect($mdSidenav.close).toHaveBeenCalled();
});
});
This throws a couple of errors:
Error: close() method does not exist
Error: Expected a spy, but got undefined.
Has anyone managed to mock out $mdSidenav and offer me some guidance please?
Thanks
UPDATE
Based on the suggested answer, I have now updated my test spec to
'use strict';
describe('NavbarController', function() {
var $controller,
vm,
$mdSidenav,
sideNavCloseMock;
beforeEach(function() {
module('app.layout');
sideNavCloseMock = jasmine.createSpy();
module(function($provide) {
$provide.value('$mdSidenav', function() {
return function(sideNavId) {
return {close: sideNavCloseMock}
}
})
});
});
beforeEach(inject(function(_$controller_, _$mdSidenav_) {
$controller = _$controller_;
$mdSidenav = _$mdSidenav_;
}));
beforeEach(function() {
vm = $controller('NavbarController', {
$mdSidenav: $mdSidenav
});
});
describe('vm.closeSideNav', function() {
beforeEach(function() {
vm.closeSideNav()
});
it('should call $mdSidenav.close()', function() {
expect(sideNavCloseMock).toHaveBeenCalled();
});
});
});
And for a sanity check, my actual controller looks as follows:
(function () {
'use strict';
angular
.module('app.layout')
.controller('NavbarController', Controller);
Controller.$inject = ['$mdSidenav'];
function Controller($mdSidenav) {
var vm = this;
vm.closeSideNav = closeSideNav;
//This only affects the sideNav when its not locked into position, so only on small\medium screens
function closeSideNav() {
$mdSidenav('left').close();
}
}
})();
Unfortunately this still isn't working for me, and I end up with a different error
TypeError: undefined is not a constructor (evaluating '$mdSidenav('left').close())
close method doesn't belong to $mdSidenav. $mdSidenav is a function that returns a side nav object. That's why it complains 'close() method does not exist'.
What you can do is mock the $mdSidenav to return an object hat has mocked close method, like this: -
var sideNavCloseMock;
beforeEach(module(function($provide){
sideNavCloseMock = jasmine.createSpy();
$provide.factory('$mdSidenav', function() {
return function(sideNavId){
return {close: sideNavCloseMock};
};
});
}));
then do
it('should call $mdSidenav.close()', function() {
expect(sideNavCloseMock).toHaveBeenCalled();
});
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();
});
});
I would like to test myService initialisation which could be different depends on conditions:
service('myService', function(internalService){
if(internalService.param){ init1(); }
else { init2(); }
//...
});
I can mock internalService, but how to recreate myService?
Services are instantiated by the injector (inject(...)) after the configuration phase. To test your initialization, all you need to do is setup differently in each tests. That is, rather than instantiating your service in a beforeEach block, do it in the test.
For instance, you could test the side effects of instantiating with init1 by mocking the internalService to have param: true
describe('Unit: myService', function() {
it('should perform init1 if internalService.param', function() {
var mockInternalService = {
param: true,
};
// config phase (mocking)
module('app', function($provide) {
$provide.constant('internalService', mockInternalService);
});
inject(function($injector) {
myService = $injector.get('myService');
// assert side effects from init1();
});
});
});
To test the side effects of init2, just mock the internalService to param: false
it('should perform init2 if !internalService.param', function() {
var mockInternalService = {
param: false,
};
// config phase (mocking)
module('app', function($provide) {
$provide.constant('internalService', mockInternalService);
});
inject(function($injector) {
myService = $injector.get('myService');
// assert side effects from init2();
});
});
EDIT: Of course, if you want to make multiple tests for each configuration, you can create two describe blocks with different beforeEach.
describe('Unit: myService', function() {
var myService;
describe('When instantiated with `init1`', function() {
beforeEach(config(true));
beforeEach(inject(injections));
it('should do X');
});
describe('When instantiated with `init2`', function() {
beforeEach(config(false));
beforeEach(inject(injections));
it('should do Y');
});
function config(param) {
return function() {
var mockInternalService = {
param: param,
};
// config phase (mocking)
module('app', function($provide) {
$provide.constant('internalService', mockInternalService);
});
};
}
function injections($injector) {
myService = $injector.get('myService');
}
});
I am developing Chrome App with AngularJS. As my app uses chrome.storage I wrote wrapper:
angular.module('myApp').factory('chromeStorageApi', function($window){
if(typeof $window.chrome.storage == 'undefined')
return false;
return{
set:function(obj, callback){
$window.chrome.storage.local.set(obj, callback)
return true;
},
.............................
}
}
I have violated TDD methodology and now I want to test my wrapper. But all my attempts were not successful. I tried to check that, for example, $window.chrome.storage.local.set() has the same arguments as chromeStorageApi.set() but I could not find a way how I can mock $window.chrome.storage.local.
UPDATED:
My last version of Unit test:
describe('chromeStorageApi', function(){
beforeEach(module('myApp'));
it('should be able to set data to the storage', inject(function(chromeStorageApi, $window){
spyOn($window.chrome.storage.local, 'set')
chromeStorageApi.set({'key':'value'}, function(){ }());
expect($window.chrome.storage.local.set).toHaveBeenCalled();
expect($window.chrome.storage.local.set).toHaveBeenCalledWith({'key':'value'}, function(){ }());
}));
});
But I get an error:
TypeError: 'undefined' is not an object (evaluating '$window.chrome.storage')
I made working tests for me. Here there are:
describe('chromeStorageApi', function(){
var mockWindow, chromeStorageApi;
beforeEach(module('myApp'));
beforeEach(function(){
mockWindow = {
chrome:{
storage:{
local: sinon.stub({
set: function(){ },
get: function(){ },
remove: function(){ },
clear: function(){ }
})
}
},
addEventListener: function(){ }
}
module(function($provide){
$provide.value('$window', mockWindow);
})
})
beforeEach(inject(function(_chromeStorageApi_){
chromeStorageApi =_chromeStorageApi_;
}))
it('should be able to set data to the storage', function(){
chromeStorageApi.set({'key':'value'}, function(){ }());
expect(mockWindow.chrome.storage.local.set).toHaveBeenCalled();
expect(mockWindow.chrome.storage.local.set).toHaveBeenCalledWith({'key':'value'}, function(){ }());
});
it('should be able to get data from the storage', function(){
chromeStorageApi.get('key', function(){ });
expect(mockWindow.chrome.storage.local.get).toHaveBeenCalled();
expect(mockWindow.chrome.storage.local.get).toHaveBeenCalledWith('key');
})
})
I am using sinonJS to create stub with methods. I hope it will be helpful for someone.
I'm new to AngularJS and running into some problems with unit testing. I've seen countless examples of mocking $httpBackend calls, but when I do that it won't work unless I also include $rootScope.$apply().
My service:
angular.module('myApp.services', ['ngResource'])
.factory('TestingService', ['$resource', function($resource) {
return $resource('/api/v1/values', {}, {
getValues: {
method: 'GET'
}
});
}]);
My unit test:
describe('Testing services', function() {
beforeEach(module('myApp.services'));
afterEach(function() {
inject(function($httpBackend) {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
});
describe('TestingService', function() {
it('would be nice to get an explanation for this',
inject(['$rootScope', '$httpBackend', 'TestingService',
function ($rootScope, $httpBackend, testingService) {
$httpBackend.expectGET('/api/v1/values').respond(100);
var result = testingService.getValues();
//$rootScope.$apply();
$httpBackend.flush();
expect(result).toBe(100);
alert(result);
}])
);
});
});
When Karma runs the test like this I get:
Error: No pending request to flush !
Error: Unsatisfied requests: GET /api/v1/values
And if I include the $rootScope.$apply(); I'll get this (and the alert of course also prints out a $promise):
Expected { $promise : { then : Function, catch : Function, finally : Function }, $resolved : true } to be 100.
Can anyone explain why I need "$rootScope.$apply();" to pass the expectGET?
And why the response is a promise instead of the mock response I've specified?
Found the problem after some sleep. Simple one fortunately.
I'm using Angular version 1.3.0-beta.2, but had an older version for angular-mocks. Updating the versions removes the need for "$root.$apply();".
The updated working test:
describe('Testing services', function() {
beforeEach(function(){
module('myApp.services');
this.addMatchers({
toEqualData: function(expected) {
return angular.equals(this.actual, expected);
}
});
});
afterEach(function() {
inject(function($httpBackend) {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
});
describe('TestingService', function() {
it('should work',
inject(['$rootScope', '$httpBackend', 'TestingService',
function ($rootScope, $httpBackend, testingService) {
$httpBackend.expectGET('/api/v1/values').respond( { key: 'value' } );
var result = testingService.getValues();
$httpBackend.flush();
expect(result).toEqualData( { key: 'value' } );
alert(angular.toJson(result, true));
}])
);
});
});