Async validation never returns in loopback - loopbackjs

I have an async validation in one of my models in which I query for a related object to validate it's existence. The problem is that the request is timing out on this validation and the server never responds.
module.exports = function(Ip) {
// Required fields
Ip.validatesPresenceOf('server_id');
...
Ip.validateAsync('server_id', isExistingServer, {
message: 'invalid server'
});
function isExistingServer(err, done) {
var ServerModel = Ip.app.models.Server;
var self = this;
process.nextTick(function() {
ServerModel.findById(self.server_id, function(e, server) {
console.log(_.isNull(server));// this actually prints false
return _.isNull(server) ? err() : done();
});
});
}
};

Because you need execute done(); then err();

According to documentation you should call done() after err. This is the example in docs:
User.validateAsync('name', customValidator, {message: 'Bad name'});
function customValidator(err, done) {
process.nextTick(function () {
if (this.name === 'bad') err();
done();
});
});
Note that as there is no return before the call to err(), done will also be 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 :)

Test async middleware in redux with thunk

I have a middleware that waits for a ARTICLE_REQUEST action, performs a fetch and dispatches either an ARTICLE_SUCCESS or an ARTICLE_FAILURE action when fetch is done. Like so
import { articleApiUrl, articleApiKey } from '../../environment.json';
import { ARTICLE_REQUEST, ARTICLE_SUCCESS, ARTICLE_FAILURE } from '../actions/article';
export default store => next => action => {
// Prepare variables for fetch()
const articleApiListUrl = `${articleApiUrl}list`;
const headers = new Headers({ 'Content-Type': 'application/json', 'x-api-key': articleApiKey });
const body = JSON.stringify({ ids: [action.articleId] });
const method = 'POST';
// Quit when action is not to be handled by this middleware
if (action.type !== ARTICLE_REQUEST) {
return next(action)
}
// Pass on current action
next(action);
// Call fetch, dispatch followup actions and return Promise
return fetch(articleApiListUrl, { headers, method, body })
.then(response => response.json());
.then(response => {
if (response.error) {
next({ type: ARTICLE_FAILURE, error: response.error });
} else {
next({ type: ARTICLE_SUCCESS, article: response.articles[0] });
}
});
}
I really wonder how to test this async code. I want to see if the follow-up actions will be dispatched properly and maybe if the fetch call gets invoked with the proper URL and params. Can anyone help me out?
PS: I am using thunk although I am not absolutely sure of its function as I just followed another code example
You can mock the fetch() function like so:
window.fetch = function () {
return Promise.resolve({
json: function () {
return Prommise.resolve({ … your mock data object here … })
}
})
}
Or you wrap the entire middleware in a Function like so:
function middlewareCreator (fetch) {
return store => next => action => { … }
}
and then create the middleware with the actual fetch method as parameter, so you can exchange it for tests or production.

Ember.js Testing async action in controller

I've a controller with an action that invoke a method that do some async stuff and return a promise.
export default Ember.Controller.extend({
_upload: function() {
// return a promise
},
actions: {
save: function(item) {
this._upload(item).then(function(response) {
// Handle success
}, function(error) {
// Handle error
}
}
}
});
I would like to unit test the code under Handle success and Handle error.
In my unit test I've mocked the _uploadMethod using
controller.set("_upload", function() {
return new Ember.RSVP.Promise(function(resolve) {
resolve({name: "image1"});
});
});
And then I invoke the action and assert that the success handler has done is job
controller.send("save", "item");
assert.equal(controller.get("selected.item"), "item");
The problem is that the assertion fails because it's run before the promise is resolved and all the stuff in success handler is completed.
How can I wait the promise to resolve before the assertion is checked?
What if you try this instead:
controller.set("_upload", function() {
const promise = new Ember.RSVP.Promise(function(resolve) {
resolve({name: "image1"});
});
promise.then(() => Ember.run.next(() => {
assert.equal(controller.get("selected.item"), "item");
}));
return promise;
});
controller.send("save", "item");
A bit hacky way, but it might work.
To test async methods, you can use the test helper waitUntil to wait for the expected return of the method, like the code below.
controller.send('changeStepAsyncActionExample');
await waitUntil(() => {
return 'what you expect to the Promise resolve';
}, { timeout: 4000, timeoutMessage: 'Your timeout message' });
// If not timeout, the helper below will be executed
assert.ok(true, 'The promise was executed correctly');

Backbone tests returning timeout

I am getting a timeout of 2000ms exceeded message when running the following tests for my Backbone application.
How can I get this test to pass?
I am also trying to listen for the event's being triggered when calling more. How could this be tested?
describe("Foo.Collection.Items/Discover", function () {
describe("More method", function () {
beforeEach(function () {
this.server = sinon.fakeServer.create();
this.hasLength = sinon.spy(Foo.View.ItemFeed.prototype, "toggleFeedback");
this.noLength = sinon.spy(Foo.View.ItemFeed.prototype, "stopLazyload");
this.item1 = new Foo.Model.item({
description: "A swell minions movie!",
for_sale: false,
title: "Minions: Goldfinger",
link: "/discover",
'private': false,
'main_image': false
});
this.item2 = new Foo.Model.item({
description: "A round pot",
for_sale: true,
title: "Pot",
link: "/discover",
'private': true,
'main_image': true
});
this.itemsDiscover = new Foo.Collection.Items([this.item1, this.item2], {url: "/discover"});
});
afterEach(function () {
this.server.restore();
this.hasLength.restore();
this.noLength.restore();
});
it("should fetch items and trigger moreFetched", function (done) {
this.server.respondWith('GET', "/discover", [
200,
{"Content-type": "application/json"},
JSON.stringify([this.item1, this.item2])
]);
this.itemsDiscover.once("add", function () {
expect(this.itemsDiscover).to.have.length(2);
expect(this.hasLength).to.be.calledOnce();
done();
});
this.itemsDiscover.more();
});
});
});
The part of the Backbone Collection I am trying to test:
more: function () {
var collection = this;
this.fetch({
success: function (collection, response) {
if (response.length) {
collection.trigger('moreFetched');
} else {
collection.trigger('emptyFetched');
}
},
reset: false,
remove: false
});
}
I guess that you have an exception that avoids to call done, try
this.itemsDiscover.once("add", function () {
try {
expect(this.itemsDiscover).to.have.length(2);
expect(this.hasLength).to.be.calledOnce();
done();
} catch(e) {
done(e);
}
});
If you get an error then post it, because I think what it is.
I think I see what's going on: your test method is dependent on the add event being triggered in order to get into the callback that verifies the data. However, the way you've set up this.itemsDiscover, it contains this.item1 and this.item2 already:
....
this.itemsDiscover = new Foo.Collection.Items([this.item1, this.item2], {url: "/discover"});
Then, your mocking out of the URL will also return the string version of an array containing this.item1 and this.item2:
...
this.server.respondWith('GET', "/discover", [
200,
{"Content-type": "application/json"},
JSON.stringify([this.item1, this.item2])
]);
...
Here's the kicker: the add event will never be fired by the collection, because the two items returned already exist in the collection. D'oh.
I suggest changing the test to wait for a sync event instead, which is always fired after a successful fetch:
this.itemsDiscover.once("sync", function () {
expect(this.itemsDiscover).to.have.length(2);
expect(this.hasLength).to.be.calledOnce();
done();
});
I expect that will get your test code running.

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

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.