Unit testing Sails/Waterline models with mocha/supertest: toJSON() issue - unit-testing

I'm setting up unit tests on my Sails application's models, controllers and services.
I stumbled upon a confusing issue, while testing my User model. Excerpt of User.js:
module.exports = {
attributes: {
username: {
type: 'string',
required: true
},
[... other attributes...] ,
isAdmin: {
type: 'boolean',
defaultsTo: false
},
toJSON: function() {
var obj = this.toObject();
// Don't send back the isAdmin attribute
delete obj.isAdmin;
delete obj.updatedAt;
return obj;
}
}
}
Following is my test.js, meant to be run with mocha. Note that I turned on the pluralize flag in blueprints config. Also, I use sails-ember-blueprints, in order to have Ember Data-compliant blueprints. So my request has to look like {user: {...}}.
// Require app factory
var Sails = require('sails/lib/app');
var assert = require('assert');
var request = require('supertest');
// Instantiate the Sails app instance we'll be using
var app = Sails();
var User;
before(function(done) {
// Lift Sails and store the app reference
app.lift({
globals: true,
// load almost everything but policies
loadHooks: ['moduleloader', 'userconfig', 'orm', 'http', 'controllers', 'services', 'request', 'responses', 'blueprints'],
}, function() {
User = app.models.user;
console.log('Sails lifted!');
done();
});
});
// After Function
after(function(done) {
app.lower(done);
});
describe.only('User', function() {
describe('.update()', function() {
it('should modify isAdmin attribute', function (done) {
User.findOneByUsername('skippy').exec(function(err, user) {
if(err) throw new Error('User not found');
user.isAdmin = false;
request(app.hooks.http.app)
.put('/users/' + user.id)
.send({user:user})
.expect(200)
.expect('Content-Type', /json/)
.end(function() {
User.findOneByUsername('skippy').exec(function(err, user) {
assert.equal(user.isAdmin, false);
done();
});
});
});
});
});
});
Before I set up a policy that will prevent write access on User.isAdmin, I expect my user.isAdmin attribute to be updated by this request.
Before running the test, my user's isAdmin flag is set to true. Running the test shows the flag isn't updated:
1) User .update() should modify isAdmin attribute:
Uncaught AssertionError: true == false
This is even more puzzling since the following QUnit test, run on client side, does update the isAdmin attribute, though it cannot tell if it was updated, since I remove isAdmin from the payload in User.toJSON().
var user;
module( "user", {
setup: function( assert ) {
stop(2000);
// Authenticate with user skippy
$.post('/auth/local', {identifier: 'skippy', password: 'Guru-Meditation!!'}, function (data) {
user = data.user;
}).always(QUnit.start);
}
, teardown: function( assert ) {
$.get('/logout', function(data) {
});
}
});
asyncTest("PUT /users with isAdmin attribute should modify it in the db and return the user", function () {
stop(1000);
user.isAdmin = true;
$.ajax({
url: '/users/' + user.id,
type: 'put',
data: {user: user},
success: function (data) {
console.log(data);
// I can not test isAdmin value here
equal(data.user.firstName, user.firstName, "first name should not be modified");
start();
},
error: function (reason) {
equal(typeof reason, 'object', 'reason for failure should be an object');
start();
}
});
});
In the mongoDB console:
> db.user.find({username: 'skippy'});
{ "_id" : ObjectId("541d9b451043c7f1d1fd565a"), "isAdmin" : false, ..., "username" : "skippy" }
Yet even more puzzling, is that commenting out delete obj.isAdmin in User.toJSON() makes the mocha test pass!
So, I wonder:
Is the toJSON() method on Waterline models only used for output filtering? Or does it have an effect on write operations such as update().
Might this issue be related to supertest? Since the jQuery.ajax() in my QUnit test does modify the isAdmin flag, it is quite strange that the supertest request does not.
Any suggestion really appreciated.

Related

setData of vue-test-utils not re-updating the component

I'm using vue-test-utils with jest, I have router-link being rendered in a Login Component. I'm passing router to the component as well.
There is data called 'email', on its update forgot link get's updated.
Following unit-test checks that.
it("updates forgot password link correctly", done => {
wrapper.setData({
user: {
email: 'a#a.com',
password: '',
}
});
Vue.nextTick(() => {
expect(wrapper.find('a').element.href).toEqual('/forgot-password/?email=a#a.com');
done();
})
})
I'm creating wrapper using following code:
const wrapper = mount(LoginComponent, {
localVue,
sync: false,
stubs: {
RouterLink: RouterLinkStub,
},
mocks: {
$route: {
path: "/login",
meta: {
signout: false
}
}
}
});
What is the correct way to update component data and then check the re-rendered component ?
Try to use async/await , for ex:
it('blah blah', async () => {
wrapper.setData({
user: {
email: 'a#a.com',
password: '',
}
});
await Vue.nextTick()
expect(wrapper.find('a').element.href).toEqual('/forgot-password/?email=a#a.com')
})
see example here https://vue-test-utils.vuejs.org/guides/testing-async-components.html

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 :)

react-router SecurityError during redux test writing

I am finishing up test for my actions test and am running into one test failing. The weird thing what is causing it to fail passes in other test.
import { browserHistory } from 'react-router';
//Passing action
export function signinUser({ email, password }) {
return function(dispatch) {
// Submit email/password to the server
return axios.post(`${ROOT_URL}/signin`, { email, password })
.then(response => {
// If request is good...
// - Update state to indicate user is authenticated
dispatch({ type: AUTH_USER });
// - Save the JWT token
localStorage.setItem('token', response.data.token);
localStorage.setItem('refreshToken', response.data.refreshToken);
// - redirect to the route '/feature'
browserHistory.push('/feature');
})
.catch(() => {
// If request is bad...
// - Show an error to the user
dispatch(authError('Bad Login Info'));
});
}
}
//failing action
export function confirmationEmail(token){
return function(dispatch) {
return axios.post(`${ROOT_URL}/confirmation`, { token })
.then(response => {
//dispatch(emailWasSent(response.data.return_msg));
// If request is good...
// - Update state to indicate user is authenticated
dispatch({ type: AUTH_USER });
// - Save the JWT token
localStorage.setItem('token', response.data.token);
localStorage.setItem('refreshToken', response.data.refreshToken);
// - redirect to the route '/feature'
browserHistory.push('/feature');
})
.catch(response => {
console.log(response)
dispatch(authError(response.data.error));});
}
}
The 2 methods are almost identical past the params passed. Both test are almost exactly the same also
describe('signinUser', () => {
it('has the correct type and payload', () => {
var scope = nock(ROOT_URL).post('/signin',function(body) {return { email: 'test#gmail.com', password: "test"}}).reply(200,{ token: "majorbs123" , refreshToken: "bs123"});
const store = mockStore({});
return store.dispatch(actions.signinUser('test#gmail.com',"test")).then(() => {
const act = store.getActions();
const expectedPayload = { type: AUTH_USER }
expect(act[0].type).to.equal(expectedPayload.type);
expect(localStorage.getItem("token")).to.equal("majorbs123");
expect(localStorage.getItem("refreshToken")).to.equal("bs123");
})
});
});
describe('confirmationEmail', () => {
it('has the correct type and payload', () => {
var scope = nock(ROOT_URL).post('/confirmation',function(body) {return { token: 'tokenbs123'}}).reply(200,{ token: "majorbs123" , refreshToken: "bs123"});
const store = mockStore({});
return store.dispatch(actions.confirmationEmail("tokenbs123")).then(() => {
const act = store.getActions();
const expectedPayload = { type: AUTH_USER }
expect(act[0].type).to.equal(expectedPayload.type);
expect(localStorage.getItem("token")).to.equal("majorbs123");
expect(localStorage.getItem("refreshToken")).to.equal("bs123");
})
});
});
The first test for signin passes no problem and the browserHistory.push has no problems. The second test throws this error stack.
SecurityError
at HistoryImpl._sharedPushAndReplaceState (/home/mikewalters015/client/node_modules/jsdom/lib/jsdom/living/window/History-impl.js:87:15)
at HistoryImpl.pushState (/home/mikewalters015/client/node_modules/jsdom/lib/jsdom/living/window/History-impl.js:69:10)
at History.pushState (/home/mikewalters015/client/node_modules/jsdom/lib/jsdom/living/generated/History.js:71:31)
at /home/mikewalters015/client/node_modules/history/lib/BrowserProtocol.js:87:27
at updateLocation (/home/mikewalters015/client/node_modules/history/lib/BrowserProtocol.js:82:3)
at pushLocation (/home/mikewalters015/client/node_modules/history/lib/BrowserProtocol.js:86:10)
at /home/mikewalters015/client/node_modules/history/lib/createHistory.js:117:15
at /home/mikewalters015/client/node_modules/history/lib/createHistory.js:90:9
at next (/home/mikewalters015/client/node_modules/history/lib/AsyncUtils.js:51:7)
at loopAsync (/home/mikewalters015/client/node_modules/history/lib/AsyncUtils.js:55:3)
at confirmTransitionTo (/home/mikewalters015/client/node_modules/history/lib/createHistory.js:80:31)
at transitionTo (/home/mikewalters015/client/node_modules/history/lib/createHistory.js:100:5)
at Object.push (/home/mikewalters015/client/node_modules/history/lib/createHistory.js:131:12)
at Object.push (/home/mikewalters015/client/node_modules/history/lib/useBasename.js:73:22)
at Object.push (/home/mikewalters015/client/node_modules/history/lib/useQueries.js:81:22)
at /home/mikewalters015/client/src/actions/authActions.js:106:2
at process._tickCallback (internal/process/next_tick.js:103:7)
It has thrown me for a loop cause the code is so similar and the line throwing the issue the react-router push method is used in other methods also and produces no problems.

Acceptance tests aren't resetting

I have some acceptance tests that test a component. If I run each test separately, they pass just fine. However, when I run the tests together, they fail because they're retaining the values from the previous tests.
Here is my code:
filter-test.js
module('Integration - Filter', {
beforeEach: function() {
App = startApp();
server = setupPretender();
authenticateSession();
},
afterEach: function() {
Ember.run(App, 'destroy');
server.shutdown();
}
});
test('filters can be saved and selected via the dropdown', function(assert) {
visit('/status');
fillIn('.filter-status', 'Not Completed');
fillIn('.filter-id', '444');
andThen(function() {
assert.ok(find('.status-title').text().includes('2 of 7'), 'the new filter filters the results');
});
});
test('only saved filters can be edited', function(assert) {
visit('/status');
fillIn('.filter-id', 'not an id');
click('.update-filter');
andThen(function() {
assert.equal(find('.alert').text(), 'Not a Saved Filter×');
});
});
test('filter values can be cleared', function(assert) {
visit('/status');
fillIn('.filter-id', '444');
fillIn('.filter-status', 'Completed');
click('.clear-filters');
andThen(function() {
// this fails because `.filter-id` is set to 'not an id':
assert.equal(find('.filter-id').val(), '', 'filter for was reset to its initial value');
// this also fails because `.filter-status` is set to 'Not Completed':
assert.equal(find('.filter-status').val(), 'Everything', 'status dropdown was reset to its initial value');
});
});
ps-filter/component.js
export default Ember.Component.extend({
classNames: ['panel', 'panel-default', 'filter-panel'],
currentFilter: null,
initialValues: null,
didInsertElement: function() {
this.set('initialValues', Ember.copy(this.get('filterValues')));
},
actions: {
saveFilter: function(name) {
var filters = this._getFilterList();
var filterValues = this.get('filterValues');
if (!Ember.isEmpty(name)) {
filters[name] = filterValues;
this.sendAction('updateFilter', filters);
this.set('currentFilter', name);
}
},
updateFilter: function() {
var filterValues = this.get('filterValues');
var currentFilter = this.get('currentFilter')
var filters = this.get('userFilters');
filters[currentFilter] = filterValues;
this.sendAction('updateFilter', filters);
},
clearFilters: function() {
this.set('currentFilter', null);
this.set('filterValues', Ember.copy(this.get('initialValues')));
}
}
});
status/controller.js
export default Ember.ArrayController.extend({
filterValues: {
filterStatus: 'Everything',
filterId: 'id',
},
userFilters: Ember.computed.alias('currentUser.content.preferences.filters')
});
status/template.hbs
<div class="row">
{{ps-filter
filterValues=filterValues
userFilters=userFilters
updateFilter='updateFilter'
}}
</div>
From what I gathered, it seems that it sets the initialValues to the filterValues left over from the previous test. However, I thought that the afterEach was supposed to reset it to its original state. Is there a reason why it doesn't reset it to the values in the controller?
Note that the component works normally when I run it in development.
Ember versions listed in the Ember Inspector:
Ember : 1.11.3
Ember Data : 1.0.0-beta.18
I'm running Ember CLI 0.2.7.
Edit
I don't think this is the issue at all, but here is my pretender setup:
tests/helpers/setup-pretender.js
export default function setupPretender(attrs) {
var users = [
{
id: 1,
name: 'ttest',
preferences: null
}
];
var activities = [
{
id: 36874,
activity_identifier: '18291',
status: 'Complete'
}, {
id: 36873,
activity_identifier: '82012',
status: 'In Progress'
}, {
id: 35847,
activity_identifier: '189190',
status: 'In Progress'
}, {
id: 35858,
activity_identifier: '189076',
status: 'Not Started'
}, {
id: 382901,
activity_identifier: '182730',
status: 'Not Started'
}, {
id: 400293,
activity_identifier: '88392',
status: 'Complete'
}, {
id: 400402,
activity_identifier: '88547',
status: 'Complete'
}
];
return new Pretender(function() {
this.get('api/v1/users/:id', function(request) {
var user = users.find(function(user) {
if (user.id === parseInt(request.params.id, 10)) {
return user;
}
});
return [200, {"Content-Type": "application/json"}, JSON.stringify({user: user})];
});
this.get('api/v1/activities', function(request) {
return [200, {"Content-Type": "application/json"}, JSON.stringify({
activities: activities
})];
});
this.put('api/v1/users/:id', function(request) {
var response = Ember.$.parseJSON(request.requestBody);
response.user.id = parseInt(request.params.id, 10);
var oldUser = users.find(function(user) {
if (user.id === parseInt(request.params.id, 10)) {
return user;
}
});
var oldUserIndex = users.indexOf(oldUser);
if (oldUserIndex > -1) {
users.splice(oldUserIndex, 1);
users.push(response.user);
}
return [200, {"Content-Type": "application/json"}, JSON.stringify(response)];
});
});
}
When I run the tests, it fails because it reset the value to the one in the previous test. For example, when I run 'filter values can be cleared', the .filter-id input has the same .filter-id value from 'only saved filter can be edited. If I change the value in 'only saved filters can be edited'back to '', the 'filter values can be cleared' test passes.
Basically, the component sets the initialValues property when it first inserts the element. It's set to a copy of the filterValues property, so it should be set to the controller's filterValues property, and shouldn't change. However, it seems that the modified filterValues property is carried over to the next test, which means that initialValues is set to that modified property when it rerenders. So, the test rerenders the templates, but retains the modified values in the controller and component.
I can make the tests pass by creating an initialValues property in the controller and passing that into the component, but that'd mean having duplicate properties in the controller (since filterValues and initialValues would have the same values).
I could modify the user record in the component, but I thought we're supposed to only modify records in the controller or router. Besides, isn't the afterEach hook supposed to reset the app?

ember-cli custom authenticator simple auth session authentication failed

What do I need to add to the code to initiate the sessionAuthenticationFailed(error). Right now it works when I have a successful login but I would like it also to show a message when when an incorrect username and/or password is entered.
here is what I have within authenticate in my custom authenticator
authenticate: function(credentials) {
var _this = this;
return new Ember.RSVP.Promise(function(resolve, reject) {
Ember.$.post( _this.serverTokenEndpoint, {
email: credentials.identification,
password: credentials.password
}).then(function(response) {
Ember.run(function() {
resolve({ token: response.session.token });
});
}, function(xhr, status, error) {
var response = JSON.parse(xhr.responseText);
Ember.run(function() {
reject(response.error);
});
});
});
}
I would also like to show an error message. What do I need to put in my loginController.
The session's authenticate method returns a promise. You can attach a then to that and handle it accordingly in your controller, e.g.:
this.get('session').authenticate('authenticator', { … }).then(function() { /*success*/ }, function() { /* error */ });
or if you're using the LoginControllerMixin:
export Ember.Route.extend(LoginControllerMixin, {
actions: {
authenticate: function() {
this._super().then(function() { /*success*/ }, function() { /* error */ });
}
}
});
The sessionAuthenticationFailed should be called automatically anyway whenever authentication fails but if you want to e.g. display an error message when authentication fails etc. I'd use above approach.