Sinon to unit test 'catch' statement - node.js

I've got a simple function such as;
module.exports = {
fetchUser:function(myUserId) {
return new Promise((resolve, reject) => {
this.getUser(myUserId)
.then(user => {
// do logic // then return user
return user;
})
.then(resolve)
.catch(err => {
// whoops there has been an error
let error = { error: 'My Error' };
reject(error);
});
});
}
};
I want to unit test both the resolve and reject result.
A simple chai test would be;
var expect = require('chai').expect;
var user = require('./user');
describe('User module', function() {
it('test fetchUser', function() {
let _user = user.fetchUser('abc123');
return _user
.then(user => {
expect(data).to.be.an('object');
});
});
Using sinon or another library, how can I for the fetchUser function to throw that reject error?

With Mocha, Chai and Sinon it can be implemented with stubbed method getUser.
const User = require("./fetchUserModule");
describe('User module', () => {
beforeEach(() => User.getUser = sinon.stub());
afterEach(() => User.getUser.reset());
it('returns user if `getUser` returns data', () => {
const user = {name: 'John'};
User.getUser.withArgs("abc123").returns(Promise.resolve(user));
return User.fetchUser("abc123").then(result => {
expect(result).to.equal(user)
}).catch(error => {
expect(error).to.be.undefined;
})
});
it('throws error if `getUser` is rejected', () => {
User.getUser.withArgs("abc123").returns(Promise.reject());
return User.fetchUser("abc123").then(result => {
expect(result).to.be.undefined;
}).catch(err => {
expect(err).to.eql({error: 'My Error'})
})
});
});

Start with anything in your "logic" that can throw an error.
If not you would need to stub this.getUser to reject or throw an error instead of returning data. sinon-as-promised patches sinon.stub to include the .resolves and .rejects promise helpers.
const sinon = require('sinon')
require('sinon-as-promised')
Setup the stub for the failure tests.
before(function(){
sinon.stub(user, 'getUser').rejects(new Error('whatever'))
})
after(function(){
user.getUser.restore()
})
Then either catch the .fetchUser error or use chai-as-promised for some sugar.
it('test fetchUser', function() {
return user.fetchUser('abc123')
.then(()=> expect.fail('fetchUser should be rejected'))
.catch(err => {
expect(err.message).to.eql('whatever')
})
})
it('test fetchUser', function() {
return expect(user.fetchUser('abc123')).to.be.rejectedWith(Error)
})
or async if you live in the new world
it('test fetchUser', async function() {
try {
await user.fetchUser('abc123')
expect.fail('fetchUser should be rejected'))
} catch(err) {
expect(err.message).to.eql('whatever')
}
})
As a side note, you don't need to wrap something that already returns a promise in new Promise and be careful about losing error information when chaining multiple .catch handlers.
fetchUser: function (myUserId) {
return this.getUser(myUserId)
.then(user => {
//logic
return user
})
.catch(err => {
let error = new Error('My Error')
error.original = err
reject(error)
});
}

Related

Resolution method is overspecified when testing with Mocha and Supertest for Node.js [duplicate]

After the upgrade, Mocha can not even run a simple test here is the code
const assert = require('assert');
it('should complete this test', function (done) {
return new Promise(function (resolve) {
assert.ok(true);
resolve();
})
.then(done);
});
I took this code from here
I understood that it now throws an exception Error: Resolution method is overspecified. Specify a callback * or * return a Promise; not both.
But how to make it work? I did not understand. I have
node -v 6.9.4
mocha -v 3.2.0
How to run this code are now in a new and correct format?
Just drop
.then(done); and replace function(done) with function()
You are returning a Promise so calling done is redundant as it said in error message
In the elder versions you had to use callback in case of async methods like that
it ('returns async', function(done) {
callAsync()
.then(function(result) {
assert.ok(result);
done();
});
})
Now you have an alternative of returning a Promise
it ('returns async', function() {
return new Promise(function (resolve) {
callAsync()
.then(function(result) {
assert.ok(result);
resolve();
});
});
})
But using both is misleading
(see for example here https://github.com/mochajs/mocha/issues/2407)
Mocha allows to either use a callback:
it('should complete this test', function (done) {
new Promise(function (resolve) {
assert.ok(true);
resolve();
})
.then(done);
});
OR return a promise:
it('should complete this test', function () {
return new Promise(function (resolve) {
assert.ok(true);
resolve();
});
});
// Or in the async manner
it('should complete this test', async () => {
await Promise.resolve();
assert.ok(true);
});
You can't do both.
I had to removed the done from the function parameter and the done() of the function call
Before
before(async function (done) {
user = new User({ ...});
await user.save();
done()
});
After
before(async function () {
user = new User({ ...});
await user.save();
});
These works for me
I had this same issue. A lot of times Mocha is paired with another library called Chai. Chai has a package called "chai-as-promised". It gives you the super simple ability to write less code and test promises. In your case of just testing if a promise resolves, it seems perfect.
const chai = require('chai');
const chaiAsPromised = require("chai-as-promised");
const should = require("chai").should();
chai.use(chaiAsPromised);
describe("Testing with correct syntax and non repeated names", () => {
it("Should give us a positive response", () => {
graphQL.sendToGQL(model,"specialEndpoint").should.eventually.be.an("Object");
})
})
An example of async functions with done breaking.
Failure Case
it('If the credentials exists in the system it should return the token generated against it.', async (done) => {
let aObj = await admin.createAdmin();
chai.request(server)
.post("/authenticate")
.set("Content-Type", "application/x-www-form-urlencoded")
.send({username: aObj.login,password:aObj.password})
.end((err, res) => {
res.should.have.status(200);
res.body.should.be.a("string");
done();
});
});
Success Case
it('If the credentials exists in the system it should return the token generated against it.', async () => {
let adminObj = await admin.createAdmin();
chai.request(server)
.post("/auth/login")
.set("Content-Type", "application/x-www-form-urlencoded")
.send({username: adminObj.login,password:adminObj.password})
.end((err, res) => {
res.should.have.status(200);
res.body.should.be.a("string");
// done();
});
});
If you don't have callbacks, prior answers (which suggest deleting the done) is correct.
If need to both await some external promise, and then exercise a callback/errback-based implementation in your test, that solution doesn't help you.
You can use a library like pify to convert the callback API to use promises.
Alternatively, you can use a Latch in your callback:
it("test", async () => {
const l = new Latch()
const v = await promiseValue()
s.methodThatTakesCallback((err, result) => {
expect(result).to.eql(expected)
l.resolve() // < notifies mocha your test is done
})
return l.promise
})
In TypeScript, here's a very stripped-down Latch implementation:
/**
* Simple one-count concurrent barrier
*/
export class Latch {
readonly promise: Promise<void>
resolve!: () => void
constructor() {
this.promise = new Promise<void>(resolve => (this.resolve = resolve))
}
}
Just emit done callback completely and use async instead.
(This implementation is based on an express api running on firebase functions, using a custom jsonwebtoken)
const { FIREBASE_UID } = require('dotenv').config()?.parsed
const chai = require('chai');
const chaiHttp = require('chai-http');
const server = require('../lib/api').API;
const should = chai.should();
const expect = chai.expect
chai.use(chaiHttp)
const test = chai.request(server).keepOpen()
// get your token with an earlier mock request and store to a var
describe('Just checking a token', () => {
let some_token
it('should print custom jwt for testing, status: 200'), async () => {
try {
const res = await test.get(`/createCustomFirebaseToken/${FIREBASE_UID}`).send()
res.should.exist
res.should.have.status(200);
res.should.have.json
some_token = (JSON.parse(res.text)).token
} catch (error) {
throw error
}
}
it('should print details:PING, status:200'), async () => {
try {
const res = await test.get('/').set('Authorization',`Bearer ${some_token}`)
.send()
res.should.exist
res.should.have.status(200);
res.should.have.json
const { details, status } = JSON.parse(res.text)
expect(details).to.equal('PING')
expect(status).to.equal(200)
} catch (error) {
throw error
}
}
after(() => test.close())
})

Jest/SuperTest Express integration tests - Can't set headers after they are sent. (when you call the same endpoint in multiple tests)

This one's killing me..
I'm writing integration tests for an Express (Typescript) app, using Jest and Supertest.
I have multiple tests for the same endpoint, to test responses from when a mocked service returns data correctly and when it rejects a promise with an Error object.
The tests run fine when each request() in each it() block hits a unique end point, but when endpoints are shared between blocks I get the following error:
Can't set headers after they are sent.
This is my test code:
let request = null;
let app = null;
const async = require('async');
import GError from '../../src/services/ErrorService';
const { list } = require('../../src/controllers/RecipeController');
let throwError: boolean = false;
let error = null;
const errorMsg: string = 'Something went wrong!';
const listData: Array<object> = [{id: 1, base: 'something'}];
jest.mock('../../src/services/RecipeService', () => {
return jest.fn().mockImplementation(() => ({
list: jest.fn(() => {
if (throwError) {
return Promise.reject(error);
}
return Promise.resolve(listData);
})
}));
});
beforeEach(() => {
request = require('supertest');
app = require('../../src/app');
});
afterEach( ( done ) => {
throwError = false;
error = null;
app.close( () => {
delete require.cache[require.resolve('../../src/app')];
done();
});
});
describe('Root Path', () => {
it('should return a welcome message', (done) => {
request(app)
.get('/')
.end((err, res) => {
expect(res.text).toEqual('Test API.');
expect(res.statusCode).toBe(200);
done();
});
});
});
describe('Recipe List', () => {
it('should call controller and return correct response when successful or error is thrown in service', (done) => {
const path: string = '/recipes/list';
request(app)
.get(path)
.end((err, res) => {
expect(JSON.parse(res.text)).toEqual({
recipes: listData
});
done();
});
});
it('should return an error response if service rejects promise', (done) => {
throwError = true;
error = new GError(errorMsg);
const path: string = '/recipes/list';
request(app)
.get(path)
.end((err, res) => {
expect(JSON.parse(res.text)).toEqual({
errors: {
message: errorMsg
}
});
done();
});
});
});
I think I need to reset the app in between tests, which is what I'm trying to achieve with:
beforeEach(() => {
request = require('supertest');
app = require('../../src/app');
});
But with no joy. Can anyone shine a light?
UPDATE:
Here's the controller method the route hits:
exports.list = async (req, res, next) => {
const recipes: IRecipeList = await recipeService.list().catch(err => {
return next(err);
});
const response: IRecipeListResponse = {recipes};
res.status(200).json(response);
};
SOLVED:
So it turned out to be nothing to do with Jest / Superagent (I was sure it was to do with one of these). Strangely though I only get this error in the context of running integration tests, there is no error when hitting the end point in Postman - which was super confusing.
PROBLEM:
exports.list = async (req, res, next) => {
const recipes: IRecipeList = await recipeService.list().catch(err => { . //this doesn't stop the execution past this await
return next(err);
});
//this still gets processed if the service rejects the promise
const response: IRecipeListResponse = {recipes};
res.status(200).json(response);
};
SOLUTION:
exports.list = async (req, res, next) => {
let error = false;
const recipes: IRecipeList = await recipeService.list().catch(err => {
error = true;
return next(err);
});
if (error) {
return;
}
const response: IRecipeListResponse = {recipes};
return res.status(200).json(response);
};
This error occurs when you send response more than once.

How to stub Mongoose methods with Sinon to test DAO?

Here is my Node unit test-case for a DAO method that returns a list of Category objects. Category is the name of my model. However, when I run this code, it is getting stuck.
describe('findAllCategories', function () {
it('should find all categoriess', function () {
var stub = sinon.stub(Category, 'find');
stub.callsFake(() => {
return Promise.resolve(allCat);
});
categoryDao.findAllCategories().then(response => {
assert.lengthOf(response, 1);
})
.catch((error) => {
console.log(error);
assert.isDefined(error);
});
stub.callsFake(() => {
return Promise.reject('');
});
categoryDao.findAllCategories().then(response => {
assert.lengthOf(response, 1);
}).catch((err) => {
console.log(err);
assert.isDefined(err);
});
});
});
Any way around?

Request promises with actions-on-google node v2

I have a similar problem like Use Async Functions in DialogFlow WebHook but that solution, changing request-promises for request-promises-native, didn't work for me, the difference it's that I'm using actions-on-google lib with ActionsSDK instead DialogFlow one, here is my code:
function call() {
var options = {
url: "https://google.es"
};
return request(options)
.then((res) => {
console.log("Success", res);
Promise.resolve();
})
.catch((err) => {
console.log("Error", err);
Promise.resolve();
});
}
const handleAction = (conv) => {
call()
.then(() => {
console.log("Going to ASK");
conv.ask('Hi, how is it going?');
return Promise.resolve();
})
.catch(error => {
console.log("Ask ERROR");
conv.ask('Hi, how is it going?');
return Promise.resolve();
});
}
app.intent('actions.intent.MAIN', (conv) => {
handleAction(conv);
});
If I change the call function for this one:
function call() {
let prom = new Promise((resolve,reject) =>{
resolve();
});
return prom;
}
It works like a charm. I don't understand where I'm wrong, I'm returning promises until the intent. Any idea?
Thanks!
you can resolve this problem using async/await. It will look like this. It might help you.
(async () => {
async function call() {
var options = {
url: "https://google.es"
};
return await new Promise((resolve, reject) => {
request(options)
.then((res) => {
resolve(res);
})
.catch((err) => {
console.log("Error", err);
reject(err)
});
}
}
const handleAction = await call(); //you will get result on handle Action varaible
})();

How to properly stub out request-promise in a mocha unit test using sinon?

My unit test is:
describe.only("Validator Service Tests", function () {
let request
before((done) => {
request = sinon.stub()
done()
})
beforeEach(() => {
process.env.API_URL = "http://test"
})
it('Should return with no errors if the file matches the schema', () => {
const updateStatusSpy = sinon.spy(FileLib, 'updateStatus')
request.yields({message: 'ok'})
return ValidatorService.handleMessage({
file: 'test'
})
.then((response) => {
assert()
console.log(response)
sinon.assert.calledOnce(updateStatusSpy)
assert(response, 'f')
})
})
})
The problem is my handleMessage function, which looks like:
exports.handleMessage = (message, done) => {
return stuff()
.then((result) => {
console.log('result', result)
if(result) {
return FileLib.updateStatus(fileId, 'valid')
}
return FileLib.updateStatus(fileId, 'invalid')
})
.then(done)
}
And my updateStatus function:
exports.updateStatus = function(fileId, status) {
console.log(fileId, status)
return request.put({
uri: `${process.env.API_URL}/stuff/${fileId}`,
body: {
status: status
}
})
}
My actual request call is buried so deep in, how can I stub it out when testing?
I'm not sure I completely understand your question, but if you are just trying to stub put, try something like this:
let stub;
beforeEach(() => {
putStub = sinon.stub(request, 'put').resolves('some_val_or_object'); //or yields or callsFake, depending on what you're using
});
it('should call request with put', async () => {
await //call your code
expect(putStub.called).to.be.true;
expect(putStub.calledWith(whatever_you_want_to_check)).to.be.true;
});

Resources