Postgres, Node, Jest - Splitting testes into two suites results in deadlock - node.js

I have two suites of tests, one for Accounts and one for Transactions, accounts.spec.js and transactions.spec.js. I'm also using supertest.
When I run them in jest, I get the error error: deadlock detected.
But if I move all describe blocks to one single spec.js file, and run only that file in jest, I get no errors.
The Account router is:
accountRouter.js
router.get('/accounts', async (req, res) => {
const result = await pool.query('SELECT * FROM accounts')
if (result.rows.length > 0) {
return res.status(200).send({ accounts: result.rows })
} else {
return res.status(404).send({ msg: 'No Account Found' })
}
})
And the Account test file is:
accounts.spec.js
describe('Accounts', () => {
beforeAll(() => {
return pool.query('BEGIN')
})
afterAll(() => {
return pool.query('ROLLBACK')
})
afterEach(() => {
return pool.query('TRUNCATE accounts')
})
it('returns 200 and all accounts', async () => {
await request(app).post(`${apiURL}/accounts`).send({title: 'Investments',})
const response = await request(app).get(`${apiURL}/accounts`).send()
expect(response.status).toBe(200)
expect(response.body.accounts.length).toBe(1)
});
})
The Transactions router is:
transactionsRouter.js
router.get('/:id/deposit', async (req, res) => {
const id = req.params.id
const result = await pool.query('SELECT * FROM accounts WHERE acc_id = ($1)', [id])
if (result.rows.length > 0) {
return res.status(200).send({destinationAccount: result.rows[0]})
} else {
return res.status(404).send({validationErrors: {invalidId: 'Account Not Found'}})
})
And the Transactions test file is:
transactions.spec.js
describe('Transactions DEPOSIT', () => {
beforeAll(() => {
return pool.query('BEGIN')
})
afterAll(() => {
return pool.query('ROLLBACK')
})
afterEach(() => {
return pool.query('TRUNCATE accounts')
})
afterEach(() => {
return pool.query('TRUNCATE transactions')
})
it('DEPOSIT returns 200 OK and account by id', async () => {
const insertMockAccountQuery = 'INSERT INTO accounts (title) VALUES ($1) RETURNING acc_id, title, budget';
const mockAccount = await pool.query(insertMockAccountQuery, [title])
const existingAccount = mockAccount.rows[0];
const response = await request(app).get(`${apiURL}/transactions/${existingAccount.acc_id}/deposit`).send();
expect(response.status).toBe(200);
expect(response.body.destinationAccount).toEqual(existingAccount);
})
});
Can anyone help me figure out the problem, please?
Thanks

Jest runs tests from one describe sequentially. Whereas runs tests from multiple files simultaneously.
In order to run all the tests sequentially the CLI flag --runInBand could be used.
More details here:
Are tests inside one file run in parallel in Jest?

Related

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 do I test node.js template render in jest

This is one of my routes.
const actor = await Actor.findById(req.params.id);
if(!actor) throw new Error("Actor not found");
res.render('admin/actors/edit_actor',{actor:actor});
The thing is I don't know how to test if valid actor gets returned because of render function.
================================================================
If I write the following
const actor = await Actor.findById(req.params.id);
if(!actor) throw new Error("Actor not found");
res.send({actor:actor});
I know how to test this because this actor would be in body parameters. such as:
//test
const res = await request(server).get('/actor/2');
res.body is the same as actor
So questions:
1) how do I test the first example which renders some view?
2) first example to test there's an integration test needed. and for the second example, we should write functional test. Am I right?
In an unit test you're supposed to mock your dependencies, so if you're testing your controller you should mock the req and res objects as well as the model. For example
Implementation
import Actor from '../model/Actor';
const controller = (req, res) => {
const actor = await Actor.findById(req.params.id);
if(!actor) throw new Error("Actor not found");
res.render('admin/actors/edit_actor',{actor:actor});
}
Unit Test
import Actor from '../model/Actor';
jest.mock('../model/Actor');
describe('controller', () => {
const req = {
params: { id: 101 }
};
const res. = {
render: jest.fn()
};
beforeAll(() => {
Actor.findById.mockClear();
controller(req, res);
});
describe('returning an actor', () => {
beforeAll(() => {
res.render.mockClear();
Actor.findById.mockResolvedValue({
name: "Some Actor"
});
controller(req, res);
});
it('should get actor by id', () => {
expect(Actor.findById).toHaveBeenCalledWith(101);
});
it('should call res.render', () => {
expect(res.render).toHaveBeenCalledWith('admin/actors/edit_actor', { actor });
})
});
describe('not returning an actor', () => {
beforeAll(() => {
res.render.mockClear();
Actor.findById.mockResolvedValue(undefined);
controller(req, res);
});
it('should throw an Error', () => {
expect(() => controller(req, res)).toThrow(Error);
});
it('should not call res.render', () => {
expect(res.render).not.toHaveBeenCalled();
});
});
});

Jest how to test express API POST request?

I need to test if my POST request to my endpoint works properly with a Jest test. I had the idea of first getting the count of my Services table (I'm using sequelize orm), then to send a new post request and to finally get the new count and compare if the old count + 1 will equal to the new count, if true then the POST request works just fine.
test('Create a valid Service', async (done) => {
const service = {
name: "cool",
description: "description"
};
await Service.count().then(async function (count) {
await request(app)
.post('/api/services')
.send(service)
.then(async () => {
await Service.count().then(function (newcount) {
expect(newcount).toBe(count + 1);
});
})
.catch(err => console.log(`Error ${err}`));
});
});
For me the test looks fine, but when I run it I get:
Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.
Is something missing or is there even a better way to test a POST request? with Jest?
It is because you are not calling the done callback passed in jest callback function. It can be done like this.
test('Create a valid Service', async(done) => {
const service = {
name: "cool",
description: "description"
};
await Service.count().then(async function (count) {
await request(app)
.post('/api/services')
.send(service)
.then(async() => {
await Service.count().then(function (newcount) {
expect(newcount).toBe(count + 1);
// execute done callback here
done();
});
})
.catch(err => {
// write test for failure here
console.log(`Error ${err}`)
done()
});
});
});
You can also write this code in this way so that the readability can be improved and maximize the use of async/await.
test('Create a valid Service', async(done) => {
const service = {
name: "cool",
description: "description"
};
try {
const count = await Service.count();
await request(app).post('/api/services').send(service)
const newCount = await Service.count()
expect(newCount).toBe(count + 1);
done()
} catch (err) {
// write test for failure here
console.log(`Error ${err}`)
done()
}
});
By default Jest also resolves the promise in async/await case. We can achieve this without the callback function also
test('Create a valid Service', async() => {
const service = {
name: "cool",
description: "description"
};
try {
const count = await Service.count();
await request(app).post('/api/services').send(service)
const newCount = await Service.count()
expect(newCount).toBe(count + 1);
} catch (err) {
// write test for failure here
console.log(`Error ${err}`)
}
});

Testing with Mocha and Chai consuming values from previous tests

I have some tests created with mocha and chai using TypeScript, they actually work as expected. Each function returns a Promise which runs a test.
My question is if is there anyway to consume on each test the value returned by a previous test without using the nesting you see below.
My concern is that if I have more test the nesting code could be very annoying going to the right
import * as request from 'supertest';
import app from '../src/app';
import { Promise } from 'bluebird';
import * as dateformat from 'dateformat';
import Commons from '../../utils/commons';
import { expect } from 'chai';
...
// all the functions used below are defined over here
...
registerNonExistingUser(email, pass, role).then(
(jwtToken: string) => {
authenticateUserCorrectJwt(jwtToken).then(
(user) => {
authenticateUserWrongJwt().then(
() => {
loginUserWrongCredentials().then(
() => {
loginUserCorrectCredentials(email, pass).then(
(jwtToken: string) => {
getLocalUserInfoCorrectJwt(jwtToken, email).then(
(user) => {
createTodoAsEditor(jwtToken, todoTitle).then(
(todos) => {
checkTodoExistsWithCorrectTitle(jwtToken, todoTitle).then(
(todo: any) => {
deleteTodoWithCorrectIdAsEditor(jwtToken, todo._id).then(
(todo) => {
unregisterExistingUser({
'local.email': email,
}).then(
(user) => {
// console.log(Commons.stringify(user));
}
);
}
);
}
);
}
);
}
);
}
);
}
);
}
);
}
);
}
);
Any idea on how to beautify this?
[EDIT 1]
#bhoo-day suggestion did the trick:
registerNonExistingUser(email, pass, role)
.then((_jwtToken: string) => {
return authenticateUserCorrectJwt(_jwtToken);
})
.then((user) => {
return authenticateUserWrongJwt();
})
...
Now I'm wondering if I could transform the beginning of the chain, in order to be something like the following (which I tried but doesn't work). My goal is to put every function at the same level, including the very first function:
Promise.resolve()
.then(() => {
return registerNonExistingUser(email, pass, role);
})
.then((jwtToken: string) => {
return authenticateUserCorrectJwt(jwtToken);
})
.then((user) => {
return authenticateUserWrongJwt();
})
...
[EDIT 2]
I tried the following and it works. Do you have any idea on how to simplify it?, maybe using: Promise.resolve()...?
new Promise((resolve) => {
it('dummy', (done) => { resolve(); return done(); });
})
.then(() => {
return registerNonExistingUser(email, pass, role);
})
.then((_jwtToken: string) => {
return authenticateUserCorrectJwt(_jwtToken);
})
Thanks!
If you use promise, you could avoid this callback hell because that is the main purpose of promise.
Some ideas to solve the issue.
// temp variable to store jwt token
let token;
registerNonExistingUser(email, pass, role)
.then((jwtToken: string) => {
token = jwtToken; // assign it so other functions can use it
return authenticateUserCorrectJwt(jwtToken)
})
.then((user) => authenticateUserWrongJwt())
.then(() => loginUserWrongCredentials())
.then(() => loginUserCorrectCredentials(email, pass))
.then((jwtToken: string) => getLocalUserInfoCorrectJwt(jwtToken, email))
.then((user) => createTodoAsEditor(token, todoTitle))
.then((todos) => checkTodoExistsWithCorrectTitle(token, todoTitle))
.then((todo: any) => deleteTodoWithCorrectIdAsEditor(token, todo._id))
.then((todo) => unregisterExistingUser({ 'local.email': email }))
.then((user) => {
// console.log(Commons.stringify(user));
});
or if you use node 7.6.0 or higher, you can use async/await. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await
async function printUser() {
try {
let jwtToken = await registerNonExistingUser(email, pass, role);
const user = await authenticateUserCorrectJwt(jwtToken);
await loginUserWrongCredentials();
jwtToken = await loginUserCorrectCredentials(email, pass);
const user = await getLocalUserInfoCorrectJwt(jwtToken, email);
const todos = await createTodoAsEditor(token, todoTitle);
const todo = await checkTodoExistsWithCorrectTitle(token, todoTitle)
const todo = await deleteTodoWithCorrectIdAsEditor(token, todo._id);
const user = await unregisterExistingUser({ 'local.email': email });
console.log(Commons.stringify(user));
} catch (error) {
// catch error here
}
}

Sinon to unit test 'catch' statement

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)
});
}

Resources