how to mock hapi.js reply with sinon for unit testing - javascript

Is there simple way of mocking the hapi reply object/function for easy unit testing?
The examples I see for hapi all use server.inject and the "lab" framwork for testing. I'm curious to see how I could keep using mocha and would like to test controller directly rather than injecting into the server.
Should i use sinon to mock the reply object?
test/post.js
before(function () {
PostController = proxyquire('../controllers/post', { 'mongoose': mongooseMock });
});
it('should be able to create a post', function(done){
var request.payload = {foo:bar};
var reply = sinon.spy(); //is this how I should mock this?
PostController.create.handler(request, reply);
reply.should ...// how do I test for statuscode 201, Boom errors, and response msgs
});
controllers/post.js
var Boom = require('Boom')
Post = require('../models/Post')
module.exports = {
create: {
auth: 'token',
handler: function (request, reply) {
var p = new Post({foo:request.payload.foo});
p.save(function (err, results) {
if (!err && results)
reply(results).created();
else {
reply(Boom.badImplementation(err));
}
});
}
}
Finally, should I just switch over to lab instead?

You can use server.inject() with Mocha too. I would just stub Post.save():
Sinon.stub(Post, 'save', function (callback) {
callback(null, { foo: 'bar' });
});
With some more code:
it('creates a post', function (done) {
Sinon.stub(Post, 'save', function (callback) {
callback(null, { foo: 'bar' });
});
server.inject({ method: 'POST', url: '/posts', payload: { foo: 'bar' } }, function (res) {
Post.save.restore();
expect(res.statusCode).to.equal(201);
done();
});
});
If you want to test for the error, you just need to modify the stub:
it('returns an error when save fails', function (done) {
Sinon.stub(Post, 'save', function (callback) {
callback(new Error('test'), null);
});
server.inject({ method: 'POST', url: '/posts', payload: { foo: 'bar' } }, function (res) {
Post.save.restore();
expect(res.statusCode).to.equal(500);
done();
});
});

Related

How to test an async function with callback in Jest?

I'm having a hard time finding info on how to test this function:
const MyService = {
async stringify (entry, cb) {
try {
const response = await axios.post('localhost:3005/stringify', {
entry
})
cb(null, response.data)
} catch (minificationError) {
if (minificationError.response.status === 500) {
cb('error 1', null)
} else {
cb('error 2', null)
}
}
}
}
I understand I can import axios and mock the .post like this:
axios.post.mockResolvedValue({
data: { some: 'value' }
})
That'd work great if I the MyService was returning the promise... but how do I deal with the callback? Is this a bad practice and should the service be returning the promise and then handle errors in the component functions instead?
Additionally, how would I mock a status code with jest (to test the failed states?)
First, you have to set up mock axios after that you have to call your mockapi's in your test case
const axios = {
post: jest.fn(() => {
return Promise.resolve({
data: {},
});
}),
create: () => axios,
request: {},
defaults: {
adapter: {},
headers: {},
},
interceptors: {
request: {
use() {},
},
response: {
use() {},
},
},
};
Once you setup mock axios then you can access in your test case and return whatever mock response and status code you want.
mockAxios.post.mockImplementation((url) => {
if (url.includes("something")) {
return Promise.resolve({ data:{"response":""}, status: 200 });
}
return Promise.reject(new Error("not found"));
});

How to return json from callback function within the Lambda?

I'm trying to return the login status from the Cognito callback function, which is written in the NodeJS Lambda. However when I call the API the response keep loading and I'm getting warning error.
Here is my code:
'use strict';
global.fetch = require('node-fetch');
const AmazonCognitoIdentity = require('amazon-cognito-identity-js');
module.exports.hello = async (event, context) => {
return {
statusCode: 200,
body: JSON.stringify({
message: "Hello there"
}),
};
// Use this code if you don't use the http event with the LAMBDA-PROXY integration
// return { message: 'Go Serverless v1.0! Your function executed successfully!', event };
};
module.exports.register = async (event, context, callback) => {
let poolData = {
UserPoolId : 'xxxxx', // Your user pool id here
ClientId : 'xxxxxxx' // Your client id here
} // the user Pool Data
let userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
let attributeList = [];
let dataEmail = {
Name : 'email',
Value : 'test#gmail.com'
};
let dataName = {
Name : 'name',
Value : 'Jack'
};
var dataPhoneNumber = {
Name : 'phone_number',
Value : '+94234324324234' // your phone number here with +country code and no delimiters in front
};
let attributeEmail = new AmazonCognitoIdentity.CognitoUserAttribute(dataEmail);
let attributeName = new AmazonCognitoIdentity.CognitoUserAttribute(dataName);
var attributePhoneNumber = new AmazonCognitoIdentity.CognitoUserAttribute(dataPhoneNumber);
attributeList.push(attributeEmail);
attributeList.push(attributeName);
attributeList.push(attributePhoneNumber);
userPool.signUp('test#gmail.com', 'H1%23$4jsk', attributeList, null, function(err, result){
let data = {};
if (err) {
callback(null, {
statusCode: 500,
body: JSON.stringify({
status: 'FAIL',
message: err.message
}),
});
} else {
let cognitoUser = result.user;
callback(null, {
statusCode: 200,
body: JSON.stringify({
status: 'SUCCESS',
message: '',
data: {
username: cognitoUser.getUsername(),
id: result.userSub
}
}),
});
}
})
// Use this code if you don't use the http event with the LAMBDA-PROXY integration
// return { message: 'Go Serverless v1.0! Your function executed successfully!', event };
};
The warning error as follows:
Serverless: Warning: handler 'register' returned a promise and also uses a callback!
This is problematic and might cause issues in your lambda.
Serverless: Warning: context.done called twice within handler 'register'!
serverless.yml
service: test-auth
plugins:
- serverless-offline
provider:
name: aws
runtime: nodejs8.10
stage: dev
region: us-east-1
functions:
hello:
handler: handler.hello
events:
- http:
path: message
method: get
register:
handler: handler.register
events:
- http:
path: register
method: post
Any help would be appreciated, Thanks in advance.
EDIT (2019-04-01):
module.exports.register = (event, context) => {
...
userPool.signUp('test#gmail.com', 'H1%23$4jsk', attributeList, null, function(err, result){
// for testing purpose directly returning
return {
statusCode: 500,
body: JSON.stringify({
status: 'FAIL',
message: err.message
})
}
})
};
Its exactly what the error message states.
All async functions return promises.
module.exports.register = async (event, context, callback) => {}
You are also using the callback by calling
callback(null, {
statusCode: 500,
body: JSON.stringify({
status: 'FAIL',
message: err.message
}),
});
Instead of using the callback, just return the either an error or a valid response.
Well the error is accurate. async wraps your return with promise. Either use callback all the way through like:
global.fetch = require('node-fetch');
const AmazonCognitoIdentity = require('amazon-cognito-identity-js');
// remove async
module.exports.register = (event, context, callback) => {
...
// if you're using callback, don't use return (setup your callback to be able to handle this value as required) instead do:
// calback({ message: 'Go Serverless v1.0! Your function executed successfully!', event })
// Use this code if you don't use the http event with the LAMBDA-PROXY integration
// return { message: 'Go Serverless v1.0! Your function executed successfully!', event };
};
Or don't use callback, use async/await (Promise) all the way through like:
module.exports.register = async (event, context) => {
...
// needs promise wrapper, when using with promise, you might want to break up your code to be more modular
const mySignUp = (email, password, attributes, someparam) => {
return new Promise((resolve, reject) => {
userPool.signUp(email, password, attributes, someparam, function(err, result) {
let data = {};
if (err) {
reject({
statusCode: 500,
body: JSON.stringify({
status: 'FAIL',
message: err.message
}),
});
} else {
let cognitoUser = result.user;
resolve({
statusCode: 200,
body: JSON.stringify({
status: 'SUCCESS',
message: '',
data: {
username: cognitoUser.getUsername(),
id: result.userSub
}
}),
});
}
})
});
}
// call the wrapper and return
return await mySignUp('test#gmail.com', 'H1%23$4jsk', attributeList, null);
// don't use double return
// Use this code if you don't use the http event with the LAMBDA-PROXY integration
// return { message: 'Go Serverless v1.0! Your function executed successfully!', event };
};
Now register will return a promise. Elsewhere in your code you can call register like:
var result = register();
result
.then(data => console.log(data))
// catches the reject from Promise
.catch(err => console.error(err))
or in async/await function (Note: `await` is valid only inside `async` function)
async function someFunc() {
try {
var result = await register();
// do something with result
console.log(result);
} catch (err) {
// reject from Promise
console.error(err)
}
}
Also note use strict is not required here as node modules use strict by default.
You are using an async function with a call back.
Try it this way:
Remove the callback from the async function.
async (event, context)
And modify the return as:
if (err) {
return {
statusCode: 500,
body: JSON.stringify({
status: 'FAIL',
message: err.message
})
}
}
And put an await on the function call.
If it helps anyone else catching this, you can add headers to the return:
return {
statusCode: 200,
headers: {"Content-Type": "application/json"},
body: JSON.stringify(response.data)
};

Trying to test retrieval of file by mocking pipe function

The db object comes from the cloudant module.
This is the code I'm trying to test:
res.set('some-header', 'some-value');
res.status(200);
db.attachment.get('some-uuid', 'file').
on('error', function(e) {
console.log('err');
reject(e);
}).
pipe(base64.decode()).pipe(_res).on('error', function(e) {
console.log('err2');
reject(e);
});
fulfill(null);
Trying to mock the code above:
var sinon = require('sinon');
var request = require('supertest');
var attachment_1 = {
get: sinon.stub(),
};
var db = {
attachment: attachment_1
};
var obj = {};
var obj2 = {};
var sample = {};
obj.pipe = function(encoderfunction) {
console.log('obj.pipe for encoding');
return obj2;
};
obj2.pipe = function(res) {
console.log('obj2.pipe');
console.log(typeof res); // object
return this;
};
obj2.on = function() {
console.log('obj2');
};
sample.on = function() {
console.log('sample.on');
return obj;
};
db.attachment.get.withArgs('some-uuid', 'file').returns(sample);
This is the actual test:
it('should respond with file contents and status of 200', function(done) {
request(app).get('/file/index.html')
.expect(200)
.end(function(err, res){
if (err) {
console.log(err);
}
console.log('res: ' + res);
done();
});
});
But I keep getting this error:
1) should respond with file contents and status of 200:
Error: timeout of 2000ms exceeded. Ensure the done() callback is being called in this test.
From the supertest docs:
If you are using the .end() method .expect() assertions that fail will
not throw - they will return the assertion as an error to the .end()
callback. In order to fail the test case, you will need to rethrow or
pass err to done(), as follows:
describe('GET /users', function() {
it('respond with json', function(done) {
request(app)
.get('/users')
.set('Accept', 'application/json')
.expect(200)
.end(function(err, res) {
if (err) return done(err);
done();
});
});
});

No result when testing user model in NodeJS and MongoDB with Mocha

I am testing my model with mocha. I have the following files:
test/utils.js
'use strict';
// Modified from https://github.com/elliotf/mocha-mongoose
var config = require('../config/db.js');
var mongoose = require('mongoose');
// ensure the NODE_ENV is set to 'test'
// this is helpful when you would like to change behavior when testing
process.env.NODE_ENV = 'test';
beforeEach(function (done) {
function clearDB() {
for (var i in mongoose.connection.collections) {
mongoose.connection.collections[i].remove(function() {});
}
return done();
}
if (mongoose.connection.readyState === 0) {
mongoose.connect(config.url, function (err) {
if (err) {
throw err;
}
return clearDB();
});
} else {
return clearDB();
}
});
afterEach(function (done) {
mongoose.disconnect();
return done();
});
test/testUserModel.js
'use strict';
// import the moongoose helper utilities
var utils = require('../test/utils');
var should = require('should');
// import our User mongoose model
var User = require('../app/models/user.js');
var testUser1 = new Object({
profilePic: "testPic",
email: "testEmail",
first_name: "fname",
last_name: "lname",
description: "description",
personality: "personality",
phone_number: "phoneNum",
password: "password",
courses: {
course_name: "courseTest"
},
role: "testRole" //student/admin/teacher
});
var testUser2 = new Object({
profilePic: "testPic2",
email: "testEmail2",
first_name: "fname2",
last_name: "lname2",
description: "description2",
personality: "personality2",
phone_number: "phoneNum2",
password: "password2",
courses: {
course_name: "courseTest2"
},
role: "testRole2" //student/admin/teacher
});
describe('Users: models', function () {
describe('#register()', function () {
it('should register a new User', function (done) {
User.register(testUser1, function (err, createdUser) {
should.not.exist(err);
createdUser.email.should.equal("testEmail");
createdUser.first_name.should.equal("fname");
createdUser.phone_number.should.equal("phoneNum");
createdUser.password.should.equal("password");
createdUser.role.should.equal("testRole");
done();
});
});
});
describe('#getAllUsers', function () {
it('should return all users in DB', function (done) {
console.log("about to add stuff");
User.register(testUser1, function (err, createdUser) {
console.log(createdUser);
});
User.register(testUser2, function (err, createdUser) {
console.log(createdUser);
});
User.getAllUsers(function (err, createdUsers) {
should.not.exist(err);
createdUsers.should.be.instanceof(Array).and.have.lengthOf(2);
done();
});
});
});
});
I have tested the register function of User and it works ok in test #register(),
However in #getAllUsers() I do a register on two user objects, testUser1 and testUSer2. But my User.getAllUsers function is returning nothing from the DB. In deployment the two functions work fine, you can add lots of users and they display in a list in GUI. It seems after the User.register function call the DB is getting cleared so when it gets to User.getAllUsers theres nothing there. Any ideas?
With your code:
describe('#getAllUsers', function () {
it('should return all users in DB', function (done) {
console.log("about to add stuff");
User.register(testUser1, function (err, createdUser) {
console.log(createdUser); // register was successfull
});
User.register(testUser2, function (err, createdUser) {
console.log(createdUser); //register was successfull
});
// Here, it should set in callback, when register function was successfull
User.getAllUsers(function (err, createdUsers) {
should.not.exist(err);
createdUsers.should.be.instanceof(Array).and.have.lengthOf(2);
done();
});
});
you can see an example with register was successfull as below:
it('should return all users in DB', function (done) {
console.log("about to add stuff");
User.register(testUser1, function (err, createdUser) {
console.log(createdUser);
// here, it should set in callback, when register is successfull
User.getAllUsers(function (err, createdUsers2) {
should.not.exist(err);
createdUsers2.should.be.instanceof(Array).and.have.lengthOf(1);
done();
});
});
});

Sinon timeouts on AJAX call

Using Mocha Chai and Sinon, I have a test to get back a specific record from a revealing module pattern. The test fails with a timeout. How should I test a method to assign variables from a AJAX request?
Test.js
(function () {
'use strict';
describe('Employee Module', function() {
var server,
employeeJSON = {
"employeeTemplate" : [
{
"userId": 1
}
]
};
before(function () {
server = sinon.fakeServer.create();
server.respondWith(
"GET",
"/employees.json",
[200, { "Content-Type": "application/json" }, JSON.stringify(employeeJSON)]
);
});
after(function () {
server.restore();
});
it('should get Employee by ID', function(done) {
var employee = new Employee(),
employeeData;
employee.getData(1).done( function (data) {
employeeData = data.employeeTemplate[0];
assert.equal(employeeData.userId, 1, 'Employee ID equals 1');
done();
});
});
});
})();
Employee.js
var Employee = function() {
var EmployeeInfo = {};
var loadUserinfo = function(userid) {
return $.ajax({
type: 'GET',
data:{userid: userid},
url: '/employees.json',
dataType: 'json',
async: true,
success: function(data) {
return data;
}
});
};
var getData = function (userid) {
return loadUserinfo(userid).done();
};
return {
getData: getData
};
};
You need to tell FakeServer when to respond. See the FakeServer API docs for reference.
For example:
it('should get Employee by ID', function(done) {
var employee = new Employee(),
employeeData;
employee.getData(1).done( function (data) {
employeeData = data.employeeTemplate[0];
assert.equal(employeeData.userId, 1, 'Employee ID equals 1');
done();
});
server.respond(); // Please respond mr. sinon so my test may continue
});

Categories

Resources