Stubbing the mongoose save method on a model - unit-testing

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.

Related

Unit test node controller/promises using express-validator

I'm using the "express-validator" middleware package to validate some parameters for this exampleController endpoint. What would be the best way to stub out this controller for unit tests? I keep getting errors like:
TypeError: errors.isEmpty is not a function
router
var controller = require('./controllers/exampleController.js');
var express = require('express');
var router = express.Router();
router.get('/example', controller.exampleController);
exampleController.js
exports.doSomething = function(req, res, next) {
var schema = {
'email': {
in: 'query',
isEmail: {
errorMessage: 'Invalid Email'
}
},
'authorization': {
in: 'headers',
// custom test
isValidAuthToken: {
errorMessage: 'Missing or malformed Bearer token'
}
}
};
// Validate headers/query params
req.check(schema);
// Handle response
req.getValidationResult()
.then(function(errors) {
if (!errors.isEmpty()) {
return res.status(400).json({ error: 'Bad Request' });
} else {
var context = {
email: req.query.email,
};
return res.render('index', context);
}
})
};
test
var chai = require('chai');
var sinonChai = require('sinon-chai');
chai.Should();
chai.use(sinonChai);
global.sinon = require('sinon');
var sinonStubPromise = require('sinon-stub-promise');
sinonStubPromise(sinon);
var rewire = require('rewire');
var exampleController = rewire('../controllers/exampleController.js');
var errorsResponse = [{
param: 'email',
msg: 'Invalid Email',
value: undefined
}];
describe('exampleController', function() {
var req;
var res;
beforeEach(function() {
req = {
headers: {},
query: {},
check: sinon.spy(),
getValidationResult: sinon.stub().returnsPromise()
};
res = {
status: sinon.stub().returns({
json: json
}),
render: sinon.spy()
};
});
afterEach(function() {
req.query = {};
});
context('when missing email query param', function() {
beforeEach(function() {
req.getValidationResult.resolves(errorsResponse);
exampleController.doSomething(req, res);
});
it('should call status on the response object with a 400 status code', function() {
res.status.should.be.calledWith(400);
});
it('should call json on the status object with the error', function() {
json.should.be.calledWith({ error: 'Bad Request' });
});
});
});
});
The way you have structured the unit test for validating a controller is not really consistent. I will try to present you the issues and workarounds in detail, but before we move on have a look at this great article on unit testing Express controllers.
Ok, so regarding the initial error you presented TypeError: errors.isEmpty is not a function that was due to a malformed response object you had setup for stubbing the getValidationResult() method.
After printing out a sample response object from this method you will notice that the correct structure is this:
{ isEmpty: [Function: isEmpty],
array: [Function: allErrors],
mapped: [Function: mappedErrors],
useFirstErrorOnly: [Function: useFirstErrorOnly],
throw: [Function: throwError] }
instead of your version of the response:
var errorsResponse = [{
param: 'email',
msg: 'Invalid Email',
value: undefined
}];
isEmpty() is a top-level function and you should have used an array attribute for storing the errors list.
I'm attaching a revamped version of your controller and test scenario so that you can correlate it with the best practices presented in the aforementioned article.
controller.js
var express = require('express');
var router = express.Router();
router.get('/example', function(req, res) {
var schema = {
'email': {in: 'query',
isEmail: {
errorMessage: 'Invalid Email'
}
}
};
// Validate headers/query params
req.check(schema);
// Handle response
req.getValidationResult()
.then(function(errors) {
if (!errors.isEmpty()) {
return res.status(400).json({
error: 'Bad Request'
});
} else {
var context = {
email: req.query.email,
};
return res.render('index', context);
}
});
});
module.exports = router;
test.js
'use strict';
const chai = require('chai');
const sinon = require('sinon');
const SinonChai = require('sinon-chai');
var sinonStubPromise = require('sinon-stub-promise');
sinonStubPromise(sinon);
chai.use(SinonChai);
chai.should();
var mockHttp = require('node-mocks-http');
var controller = require('./controller.js');
describe.only('exampleController', function() {
context('when missing email query param', function() {
var req;
var res;
beforeEach(function() {
// mock the response object
// and attach an event emitter
// in order to be able to
// handle events
res = mockHttp.createResponse({
eventEmitter: require('events').EventEmitter
});
});
it('should call status on the response object with a 400 status code',
(done) => {
// Mocking req and res with node-mocks-http
req = mockHttp.createRequest({
method: 'GET',
url: '/example'
});
req.check = sinon.spy();
var errorsResponse = {
isEmpty: function() {
return false;
},
array: [{
param: 'email',
msg: 'Invalid Email',
value: undefined
}]
};
// stub the getValidationResult()
// method provided by the 'express-validator'
// module
req.getValidationResult = sinon.stub().resolves(errorsResponse);
// spy on the response status
sinon.spy(res, 'status');
sinon.spy(res, 'json');
// called when response
// has been completed
res.on('end', function() {
try {
// assert status and JSON args
res.status.should.have.been.calledWith(400);
res.json.should.have.been.calledWith({error: 'Bad Request'});
done();
} catch (e) {
done(e);
}
});
// Call the handler.
controller.handle(req, res);
});
});
});
A few points to notice in the updated version of the test.
Instead of manually constructing request / response objects, you should better use a library that's already there for this job. In my version I'm using 'node-mocks-http' which is pretty much a standard when it comes to Express.
When testing controllers, instead of manually calling the service method it's better to use the natural routing mechanism through the mocked HTTP request object. This way you can cover both happy & sad routing paths
Using a common HTTP req / res mocking library, means less work for you - all you need to do is extend the factory objects with non-standard functions (e.g. getValidationResult() from express-validator) and add your spies / stubs seamlessly
Finally, the library supports attaching event listeners on response events that otherwise you could not simulate manually. In this example, we're listening for the end event from the response object that is called after the return res.status(400).json({error: 'Bad Request'}); method has been called in your controller.
Hope I've cleared things up a bit :)

Having trouble using Jest with external dependancies

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

How to assert and test a ReactJS / Fluxible Component's state?

My app is a Fluxible / React application.
I have the following spec that attempts to test a LoginForm. Embedded components have been stubbed using rewire. I referenced http://fluxible.io/api/components.html#testing.
The first spec it("renders") passes. However, when I try to do more tests as shown in the commented code, the test fails.
I am unable to assert on LoginForm's state or trigger simulated events using TestUtils on the component. Are there any ways to do that?
import React from 'react/addons';;
import { createMockComponentContext } from 'fluxible/utils';
import createStore from 'fluxible/addons/createStore';
var rewire = require("rewire");
var rewireModule = require("../../helpers/rewire-module");
// stub inner components with LoginForm
// `rewire` instead of `require`
var LoginForm = rewire("../../../src/components/auth/login-form");
// Replace the required module with a stub component.
rewireModule(LoginForm, {
FormattedMessage: React.createClass({
render: function() { return <div />; }
}),
NavLink: React.createClass({
render: function() { return <div />; }
})
});
describe('LoginForm', function() {
var context;
var TestUtils;
var provideContext;
var connectToStores;
var MockIntlStore;
var MockAuthStore;
var noop = function(){};
var component;
beforeEach(function(){
MockIntlStore = createStore({
storeName: 'IntlStore',
getMessage: noop,
getState: function(){
return {}
}
});
MockAuthStore = createStore({
storeName: 'AuthStore'
});
context = createMockComponentContext({
stores: [MockIntlStore, MockAuthStore]
});
// React must be required after window is set
TestUtils = React.addons.TestUtils
provideContext = require('fluxible/addons/provideContext');
connectToStores = require('fluxible/addons/connectToStores');
// Wrap with context provider and store connector
LoginForm = provideContext(connectToStores(LoginForm, [MockIntlStore, MockAuthStore], function (stores) {
return {
};
}));
component = TestUtils.renderIntoDocument(
<LoginForm context={context} />
);
});
it("renders", function() {
var foundComponent = TestUtils.findRenderedDOMComponentWithClass(
component, 'login-form');
expect(foundComponent).toBeDefined();
});
// TODO fluxible wraps components so we cant reach the inner component to assert on state and trigger event handlers
// it("should have an initial state", function() {
// let initialState = {
// username: '',
// pass: ''
// }
// expect(component.state).toEqual(initialState);
// });
});
When you use provideContext and connectToStores, your component is wrapped. You have done it right to find the component using TestUtils. findRenderedDOMComponentWithClass, Simply use the foundComponent for test, that is what is being tested. i.e.
...
var foundComponent = TestUtils.findRenderedDOMComponentWithClass(
component, 'login-form');
expect(foundComponent.state).toEqual(initialState);
...
If you're still looking for a solution:
var tbg = React.createElement(x, { di: serviceLocator });
var renderer = React.addons.TestUtils.createRenderer();
var rtbg = renderer.render(tbg);
Then your method is here:
renderer._instance._instance.myMethod
Where myMethod is a function member of component x

Mocking $window in angularjs with qunit

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?

How to mock angular $resource in jasmine tests

I am trying to test a controller which uses angular's $resource.
function PermissionsCtrl($scope, $resource, $cookies) {
var Object = $resource('/v1/objects/:id');
loadObjects();
function loadObjects() {
$scope.myAppObjects = new Array();
var data = AppObject.get({find: '{"app_id": '+wtm.app_id+'}'},
function(){
if (data.results) {
for(var i = 0; i< data.results.length; i++) {
if(!data.results[i].is_deleted) {
(function(i){
$scope.objects(data.results[i]);
}(i));
}
}
}
},
function(error){console.log(error);});
}
And here is the test code.
var apiServer = "...";
var app_id = 999
var mock_object_data = {...};
describe('apps permissionsCtrl', function(){
var scope, ctrl, $httpBackend;
// Create a matcher for comparing data
beforeEach( function() {
this.addMatchers({
toEqualData: function(expected) {
return angular.equals(this.actual, expected);
}
});
});
// Create the controller with injected data/services
beforeEach(inject(function(_$httpBackend_, $rootScope, $controller, $resource) {
$httpBackend = _$httpBackend_;
// cookie data to inject
var cookies = new Array();
cookies['id'] = '...'; // just needs to be declared
$httpBackend.expectGET(apiServer+'/v1/app_objects? find=%7B%22app_id%22:'+app_id+'+%7D&access_token=' + cookies['id'])
.respond( mock_object_data );
var $injector = angular.injector(['ng', 'ngResource']);
var $resource = $injector.get('$resource');
scope = $rootScope.$new();
ctrl = $controller(PermissionsCtrl, {$scope: scope, $cookies: cookies, $resource: $resource});
}));
it('should put object data into $scope', function() {
$httpBackend.flush();
expect(scope.objects).toEqualData( mock_object_data );
});
});
When I run this I get
Error: Unknown provider: $resourceProvider <- $resource
at the line where I try to create my controller. I don't understand how to inject this into my controller and no matter what I try I get the same error. A couple things I've tried are
Declaring a an empty mock object and passing it through similar to my cookies variable. I figure this is probably a bad solution anyway since I actually want to use the service.
Mimicking the scope mock and passing it into my inject function and passing $resource.$new() to my controller.
Doing nothing and hoping that httpBackend would cover it since that's what ultimately gets called anyway. Vojta Jína made it sound like that would work but no dice.
Mild epithets. Satisfying but not very effective.
After more reading and more experimenting it seems the right way to do this is to abstract the use of $resource out of the controller. In my case I wrote a service that relies on $resource and then inject that service into my controller. Meanwhile I test that service separately from my controller. Better practice all around.
My service declaration:
angular.module('apiModule', ['localResource', 'ngCookies'])
.factory('apiService', function($resource, $cookies) {
and in my unit tests I pass it through in a beforeEach setup function
beforeEach(module('apiModule'));
Try to instantiate $resource with the following code:
var $injector = angular.injector(['ng', 'ngResource']);
var $resource = $injector.get('$resource');
Similar for other services, except when they are in other modules still. Then add that module to the array.
More info