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.
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 want to make a test of a service in AngularJS but there is always a problem with the injector.
Here is the service:
var projectApp = angular.module("projectApp", []);
projectApp.service("workTimeService", function(){
this.workHours = function(workHours){
return workHours;
}
}
Here is the test:
describe("Unit: Testing services", function(){
var workTimeService;
beforeEach(function(){
module("projectApp");
});
beforeEach(inject(function(_workTimeService_){
workTimeService = _workTimeService_;
}));
it('Should have funtion', function(){
expect(angular.isFunction(workTimeService.workHours)).toBe(true);
});
});
I have made it just as in the tutorials but jasmine says that workTimeService is undefined.
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...
});
This is my first question here. I'll try to do my best. I searched a lot before posting
I'm developing an angularJS application relying on the Javascript ParseSDK.
I have convinced myself to dive in testing recently, so I am a beginner.
I have this factory UserFactory that wraps around the SDK so everything is clean and modular the Angular way. ie: SDK is only used through factories (not controller nor directives).
It goes like this:
myModule.factory('UserFactory', ['$q', function($q){
var User = Parse.User.extend({
// Instance methods
},{
// static/class methods
// Overrides Parse.User.current() to wrap current User in promise
current: function(){
var deferred = $q.defer();
var currentUser = Parse.User.current();
if(currentUser)
deferred.resolve(currentUser);
else
deferred.reject("No current user");
return deferred.promise;
}
});
return User;
}]);
My question: How to test for UserFactory.current() knowing it uses an external service?
I've looked into mocking the Parse SDK but don't know how to do it since it's not angular related (ie: can't use httpBackend).
My current test file:
describe("Unit: UserFactory", function(){
var UserFactory;
beforeEach(function(){
module("myModule");
inject(function(_UserFactory_){
UserFactory = _UserFactory_;
});
});
it("should return current User", function(){
// What to expect ?
});
});
Thank you in advance
describe("Unit: UserFactory", function(){
var UserFactory;
beforeEach(function(){
module("myModule");
inject(function(_UserFactory_){
UserFactory = _UserFactory_;
$rootScope = _$rootScope_;
});
});
describe('current()', function() {
var successCallback, errorCallback;
beforeEach(function() {
successCallback = jasmine.createSpy('success');
errorCallback = jasmine.createSpy('error');
});
it("promise should resolve if Parse.User.current is truthy", function(){
spyOn(Parse.User, 'current').and.returnValue(true);
UserFactory.current().then(successCallback, errorCallback);
$rootScope.$digest();
expect(successCallback.calls.count()).toBe(1);
expect(errorCallback.calls.count()).toBe(0);
expect(Parse.User.current).toHaveCalledOnce();
});
it("promise should reject if Parse.User.current is falsy", function(){
spyOn(Parse.User, 'current').and.returnValue(false);
UserFactory.current().then(successCallback, errorCallback);
$rootScope.$digest();
expect(errorCallback.calls.count()).toBe(1);
expect(successCallback.calls.count()).toBe(0);
expect(Parse.User.current).toHaveCalledOnce();
});
});
});
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));
}])
);
});
});