I'm pretty new to angular and been wanting to test drive and I've hit a snag mocking out $window. The item in which I'm attempting to test is very simple but important -- I need to know if localStorage is there or not and need to be able to fake out $window to do so.
The code is very basic so far and what I have is this for the service ...
'use strict';
mainApp.factory('somedataStorage',function($window) {
var local = $window.localStorage;
return {
hasLocal: function() {
return local != undefined;
},
};
});
How I'm testing it is this ...
(function () {
var fakeWin = {
localStorage: null
};
var $injector = angular.injector(['ngMock', 'ng', 'mainApp']);
//var $window = $injector.get('$window');
var init = {
setup: function () {
//this.$window = fakeWin;
},
}
module('LocalStorageTests', init);
test("if localstorage isn't there, say so", function () {
var $service = $injector.get('somedataStorage' /*, {$window: fakeWin} */);
ok(!$service.hasLocal, "no local storage");
});
})();
So what am I missing?
Related
I've been searching a long time for an answer to the question I'm about to ask without success.
Let's say I have the following directive :
(function () {
'use strict';
angular
.module('com.acme.mymod')
.directive('myDirective', myDirective);
function myDirective() {
var directive = {
restrict: 'EA',
scope: {},
replace: true,
templateUrl: 'folder/myDirective/myDirective.tpl.html',
controller: "myDirectiveController",
controllerAs: 'vm',
bindToController: {
somedata: "#?",
endpoint: "#?"
},
link: link
};
return directive;
function link(scope, element) {
activate();
function activate() {
scope.$on('$destroy', destroy);
element.on('$destroy', destroy);
}
}
function destroy() {
}
}
})();
myDirectiveController is as follow:
(function () {
'use strict';
angular
.module('com.acme.mymod')
.controller('myDirectiveController', myDirectiveController);
myDirectiveController.$inject = ['utils', '$log'];
// utils is an external library factory in another module
function myDirectiveController(utils, $log) {
var vm = this;
vm.menuIsOpen = false;
function activate() {
vm.dataPromise = utils.getValuePromise(null, vm.somedata, vm.endpoint);
vm.dataPromise.then(function (result) {
vm.data = result.data;
}, function () {
$log.debug("data is not Valid");
});
}
activate();
}
})();
The spec file is as follow:
describe('myDirective Spec', function () {
'use strict';
angular.module('com.acme.mymod', []);
var compile, scope, directiveElem, utils;
beforeEach(function(){
module('com.acme.mymod');
inject(function($compile, $rootScope, $injector,utils){
compile = $compile;
scope = $rootScope.$new();
scope.$digest();
directiveElem = getCompiledElement();
//utils=utils;
console.log(utils.test());
});
});
function getCompiledElement(){
var element = angular.element('<div my-directive="" data-some-data=\' lorem\'></div>');
var compiledElement = compile(element)(scope);
scope.$digest();
return compiledElement;
}
it('should have a nav element of class', function () {
var navElement = directiveElem.find('nav');
expect(navElement.attr('class')).toBeDefined();
});
it('should have a valid json data-model' ,function () {
var data=directiveElem.attr('data-somedata');
var validJSON=false;
try{
validJSON=JSON.parse(dataNav);
}
catch(e){
}
expect(validJSON).toBeTruthy();
});
});
What I can't quite figure out is that every test I try to run, the directive is not compiled or created correctly I'm not sure.
I get :
Error: [$injector:unpr] Unknown provider: utilsProvider <- utils
I tried:
Injecting the controller or utils with $injector.get()
Looked at this post $injector:unpr Unknown provider error when testing with Jasmine
Any tips, pointers or clues as to what I'm doing wrong would be more than welcome
I found the solution after feeling a hitch in my brain :)
In the beforeEach function, all I needed to do is to reference my utils module name this way:
module('com.acme.myutilsmod');
This line "expose" modules components so consumers can use it.
I'm trying to test React with Flux code using Jest. I'm reasonably new to unit testing.
I think I'm doing something wrong with Mocking my dependancies (to be honest the mocking thing kind of confuses me).
Here is what I'm having trouble with:
//LoginStore-test.js
jest.dontMock('../../constants/LoginConstants');
jest.dontMock('jsonwebtoken');
jest.dontMock('underscore');
jest.dontMock('../LoginStore');
describe("login Store", function(){
var LoginConstants = require('../../constants/LoginConstants');
var AppDispatcher;
var LoginStore;
var callback;
var jwt = require('jsonwebtoken');
var _user = {
email: 'test#test.com'
};
//mock actions
var actionLogin = {
actionType: LoginConstants.LOGIN_USER,
'jwt': jwt.sign(_user, 'shhh', { expiresInMinutes: 60*5 })
};
beforeEach(function(){
AppDispatcher = require('../../dispatchers/AppDispatcher');
LoginStore = require('../LoginStore');
callback = AppDispatcher.register.mock.calls[0][0];
});
...
it('should save the user', function(){
callback(actionLogin);
var user = LoginStore.getUser();
expect(user).toEqual(_user);
});
});
});
LoginStore.js file:
var AppDispatcher = require('../dispatchers/AppDispatcher');
var BaseStore = require('./BaseStore');
var LoginConstants = require('../constants/LoginConstants.js');
var _ = require('underscore');
var jwt = require('jsonwebtoken');
//initiate some variables
var _user;
var _jwt;
var LoginStore = _.extend({}, BaseStore, {
getUser: function(){
return _user;
}
});
AppDispatcher.register(function(action){
switch(action.actionType){
case LoginConstants.LOGIN_USER:
//set the user
_user = jwt.decode(action.jwt);
//save the token
_jwt = action.jwt;
break;
//do nothing with the default
default:
return true;
}
LoginStore.emitChange();
return true;
});
module.exports = LoginStore;
The jsonwebtoken functionality doesn't seem to be working at all. If I log actionLogin.jwt it just returns undefined. Any idea what I'm doing wrong here?
Cheers
After a bit of searching around, and actually trying to figure out a different issue I found the answer. just add
"jest": {"modulePathIgnorePatterns": ["/node_modules/"]}
to your package.json file
I would like to stub the save method available to Mongoose models. Here's a sample model:
/* model.js */
var mongoose = require('mongoose');
var userSchema = mongoose.Schema({
username: {
type: String,
required: true
}
});
var User = mongoose.model('User', userSchema);
module.exports = User;
I have some helper function that will call the save method.
/* utils.js */
var User = require('./model');
module.exports = function(req, res) {
var username = req.body.username;
var user = new User({ username: username });
user.save(function(err) {
if (err) return res.end();
return res.sendStatus(201);
});
};
I would like to check that user.save is called inside my helper function using a unit test.
/* test.js */
var mongoose = require('mongoose');
var createUser = require('./utils');
var userModel = require('./model');
it('should do what...', function(done) {
var req = { username: 'Andrew' };
var res = { sendStatus: sinon.stub() };
var saveStub = sinon.stub(mongoose.Model.prototype, 'save');
saveStub.yields(null);
createUser(req, res);
// because `save` is asynchronous, it has proven necessary to place the
// expectations inside a setTimeout to run in the next turn of the event loop
setTimeout(function() {
expect(saveStub.called).to.equal(true);
expect(res.sendStatus.called).to.equal(true);
done();
}, 0)
});
I discovered var saveStub = sinon.stub(mongoose.Model.prototype, 'save') from here.
All is fine unless I try to add something to my saveStub, e.g. with saveStub.yields(null). If I wanted to simulate an error being passed to the save callback with saveStub.yields('mock error'), I get this error:
TypeError: Attempted to wrap undefined property undefined as function
The stack trace is totally unhelpful.
The research I've done
I attempted to refactor my model to gain access to the underlying user model, as recommended here. That yielded the same error for me. Here was my code for that attempt:
/* in model.js... */
var UserSchema = mongoose.model('User');
User._model = new UserSchema();
/* in test.js... */
var saveStub = sinon.stub(userModel._model, 'save');
I found that this solution didn't work for me at all. Maybe this is because I'm setting up my user model in a different way?
I've also tried Mockery following this guide and this one, but that was way more setup than I thought should be necessary, and made me question the value of spending the time to isolate the db.
My impression is that it all has to do with the mysterious way mongoose implements save. I've read something about it using npm hooks, which makes the save method a slippery thing to stub.
I've also heard of mockgoose, though I haven't attempted that solution yet. Anyone had success with that strategy? [EDIT: turns out mockgoose provides an in-memory database for ease of setup/teardown, but it does not solve the issue of stubbing.]
Any insight on how to resolve this issue would be very appreciated.
Here's the final configuration I developed, which uses a combination of sinon and mockery:
// Dependencies
var expect = require('chai').expect;
var sinon = require('sinon');
var mockery = require('mockery');
var reloadStub = require('../../../spec/utils/reloadStub');
describe('UNIT: userController.js', function() {
var reportErrorStub;
var controller;
var userModel;
before(function() {
// mock the error reporter
mockery.enable({
warnOnReplace: false,
warnOnUnregistered: false,
useCleanCache: true
});
// load controller and model
controller = require('./userController');
userModel = require('./userModel');
});
after(function() {
// disable mock after tests complete
mockery.disable();
});
describe('#createUser', function() {
var req;
var res;
var status;
var end;
var json;
// Stub `#save` for all these tests
before(function() {
sinon.stub(userModel.prototype, 'save');
});
// Stub out req and res
beforeEach(function() {
req = {
body: {
username: 'Andrew',
userID: 1
}
};
status = sinon.stub();
end = sinon.stub();
json = sinon.stub();
res = { status: status.returns({ end: end, json: json }) };
});
// Reset call count after each test
afterEach(function() {
userModel.prototype.save.reset();
});
// Restore after all tests finish
after(function() {
userModel.prototype.save.restore();
});
it('should call `User.save`', function(done) {
controller.createUser(req, res);
/**
* Since Mongoose's `new` is asynchronous, run our expectations on the
* next cycle of the event loop.
*/
setTimeout(function() {
expect(userModel.prototype.save.callCount).to.equal(1);
done();
}, 0);
});
}
}
Have you tried:
sinon.stub(userModel.prototype, 'save')
Also, where is the helper function getting called in the test? It looks like you define the function as the utils module, but call it as a method of a controller object. I'm assuming this has nothing to do with that error message, but it did make it harder to figure out when and where the stub was getting called.
I started to write unit tests for my angular app.
However it seems to me that I use a lot of boilerplate code to init and test the controller.
In this Unit Test I want to test if a model from the scope is sent to the Api when I execute a function.
I needed 20 lines of code for this. This makes it inconvenient to write unit tests that do only one thing.
Do you have any tips on getting the code size to a smaller chunk?
This is my current unit test:
'use strict';
describe('controllers', function(){
beforeEach(module('kronos'));
describe('CustomerSignupCtrl', function() {
it('should send customer to Api on submit', inject(function($controller) {
var scope = {};
var $location = {};
var Api = {
signupCustomer: function(customer) {
expect(customer).toEqual({attrs: "customerdata"});
return {
success: function() { return this; },
error: function() { return this; }
};
}
};
var ctrl = $controller('CustomerSignupCtrl', {
$scope: scope,
$location: location,
Api: Api});
scope.customer = {attrs: "customerdata"};
scope.signup();
}));
});
});
What I don't like in particular are the following points
I need to init the all dependencies and it doesn't matter if I use them or not
The Api returns a promise that I only need because the controller is expecting the promise
I need to init the controller.
How can I make this code shorter and more explicit?
Edit: I just noticed I can ignore the $location Service for this unit test. Great
Edit2: I found out about angular-app, which serves as a good practice example app. There you can find specs with jasmine, which are really nice written.
Use another beforeEach method in your describe scope to set up scope, $location, controller etc, then just change them in your test as you need to. Js is dynamic so all should be fine.
You can also extract each object that you set up into a function so that you can reinitialise them in a test if you need to.
describe('controllers', function(){
beforeEach(module('kronos'));
describe('CustomerSignupCtrl', function() {
var controller, scope, $location, Api;
beforeEach(function(){
scope = {};
$location = {};
Api = {
signupCustomer: function(customer) {
expect(customer).toEqual({attrs: "customerdata"});
return {
success: function() { return this; },
error: function() { return this; }
};
}
};
controller = makeController();
})
function makeController(){
inject(function($controller){
controller = $controller('CustomerSignupCtrl', {
$scope: scope,
$location: location,
Api: Api});
});
}
it('should send customer to Api on submit', function() {
scope.customer = {attrs: "customerdata"};
scope.signup();
});
});
});
You can not shorten your code much. Things like initialization, mocking and assertion have to be done at some place. But you can improve the readability of your code by decoupling initialization and test code. Something like this:
describe('CustomerSignupCtrl', function(){
var controller, scope, location, api;
beforeEach(module('kronos'));
// initialization
beforeEach(inject(function($controller, $rootScope, $location, Api){
scope = $rootScope.$new();
location = $location;
api = Api;
controller = $controller('CustomerSignupCtrl', {
$scope: scope, $location: location, Api: api});
}));
// test
it('should send customer to Api on submit', function() {
scope.customer = {attrs: "customerdata"};
spyOn(api,'signupCustomer').andCallFake(function(customer) {
return {
success: function() { return this; },
error: function() { return this; }
};
});
scope.signup();
expect(api.signupCustomer).toHaveBeenCalledWith(scope.customer);
});
});
Question: How do I fake my pointFactory so I can Jasmine Unit Test it.
I have the Following Directive.
It takes the html sends it to a factory and the uses the response for some logic
CommonDirectives.directive('TextEnrichment',['PointFactory','appSettings', function (pointFactory,settings) {
return {
restrict: 'A',
link : function (scope, element, attrs) {
var text = element.html();
pointFactory.getPoints(text).then(function(response){
})}}}]);
So far my unit tests looks like this, however it doesn't work since I'm not injecting the factory.
beforeEach(module('app.common.directives'));
beforeEach(function () {
fakeFactory = {
getPoints: function () {
deferred = q.defer();
deferred.resolve({data:
[{"Text":"Some text"}]
});
return deferred.promise;
}
};
getPointsSpy = spyOn(fakeFactory, 'getPoints')
getPointsSpy.andCallThrough();
});
beforeEach(inject(function(_$compile_, _$rootScope_,_$controller_){
$compile = _$compile_;
$rootScope = _$rootScope_;
}));
it('Factory to have been Called', function () {
var element = $compile('<div data-text-enrichment=""> Text </div>')($rootScope)
expect(getPointsSpy.callCount).toBe('1');
});
Update
Following advice from Felipe Skinner I have updated the test with the following
beforeEach(function(){
module(function($provide){
$provide.factory('PointFactory',getPointsSpy)
})
});
However I get the following error:
TypeError: 'undefined' is not a function (evaluating
'pointFactory.getPoints(text)')
You can use the $provide to inject your controller dependencies.
Here's my beforeEach for example:
describe('MyCtrl', function() {
var $controller,
$scope,
$httpBackend,
windowMock,
registerHtmlServiceMock,
mixPanelServiceMock,
toastMock;
beforeEach(function() {
windowMock = { navigator: {} };
registerHtmlServiceMock = {};
mixPanelServiceMock = jasmine.createSpyObj('mixpanel', ['track']);
toastMock = jasmine.createSpyObj('toast', ['error']);
module('myModule');
module(function($provide) {
$provide.value('$window', windowMock);
$provide.value('RegisterHtmlService', registerHtmlServiceMock);
$provide.value('MixPanelService', mixPanelServiceMock);
$provide.value('ToastService', toastMock);
});
inject(function(_$controller_, _$rootScope_, _$httpBackend_) {
$scope = _$rootScope_.$new();
$controller = _$controller_('CourseSelectionCtrl', { $scope: $scope });
$httpBackend = _$httpBackend_;
});
});
// my test cases
});
I haven't tried mocking a function that returns some value. Those two mocks (mixpanel-track and toast-error) are for "void" functions.
UPDATE:
Try changing the previous $provide with this type of injection then.
Change from this:
module(function($provide) {
$provide.value('$window', windowMock);
$provide.value('RegisterHtmlService', registerHtmlServiceMock);
$provide.value('MixPanelService', mixPanelServiceMock);
});
inject(function(_$controller_, _$rootScope_, _$httpBackend_) {
$scope = _$rootScope_.$new();
$controller = _$controller_('CourseSelectionCtrl', { $scope: $scope });
$httpBackend = _$httpBackend_;
});
To this:
beforeEach(inject(function(_$controller_, _$rootScope_, _$httpBackend_) {
mixPanelService = mixPanelServiceMock;
$scope = _$rootScope_.$new();
$controller = _$controller_('MyCtrl', { $scope: $scope, MixPanelService: mixPanelService });
$httpBackend = _$httpBackend_;
}));
The rest of the code should be the same, except for that. Let me know if this works