Open handles while testing Koa.js with Jest - node.js

Here's a trivial trivial koa server implementation:
const Koa = require('koa')
const api = require('./resources')
const createServer = () => {
const app = new Koa()
app.use(api.routes())
app.use(api.allowedMethods())
return app
}
module.exports = createServer
And a simple test:
const request = require('supertest')
const createServer = require('../src/server')
const { knex } = require('../src/config/db')
let server
beforeAll(() => {
server = createServer().listen(8081)
return server
})
beforeEach(() => {
return () => {
knex.migrate.latest()
knex.seed.run()
}
})
afterEach(() => {
return () => {
knex.migrate.rollback()
}
})
afterAll(() => {
server.close()
})
describe('routes: /api/users', () => {
describe('GET /users', () => {
test('should return json', async () => {
const response = await request(server).get('/api/users')
expect(response.status).toEqual(200)
expect(response.type).toEqual('application/json')
expect(response.body).toHaveLength(3)
})
})
})
I will skip the example route for brevity sake as the test passes successfully. But then it hangs, and running jest --detectOpenHandles produces:
Jest has detected the following 1 open handle potentially keeping Jest from exiting:
● TCPSERVERWRAP
6 |
7 | beforeAll(() => {
> 8 | server = createServer().listen(8081)
| ^
9 | return server
10 | })
11 |
at Application.listen (node_modules/koa/lib/application.js:82:19)
at Object.<anonymous> (__tests__/routes.test.js:8:27)
Why does this occur and how can I fix it?

There are several problems.
return server doesn't affect anything, it can be removed. But beforeAll doesn't wait for the server to start, it should be:
beforeAll(done => {
server = createServer().listen(8081, done)
})
beforeEach and afterEach return functions that are not executed, promises from Knex aren't chained, it should be:
beforeEach(async () => {
await knex.migrate.latest()
await knex.seed.run()
})
afterEach(async () => {
await knex.migrate.rollback()
})
afterAll doesn't wait for server connection to be closed, which is likely the cause for this error, it should be:
afterAll(done => {
server.close(done)
});

Related

Jest await not working/returning anything test is frozen

I am trying to use await in my test and when I run the test runner the test becomes stuck and no test result is returned. Here is my code
describe("retrieveCandidate", () => {
describe("when settings are found", () => {
beforeEach(() => {
configurationApi = new ConfigurationApi(BaseUrl);
});
afterEach(() => {
configurationApi = undefined;
});
it("should return set of configuration values", () => {
const configurationValueSet: IConfigurationValueSet | undefined =
await configurationApi?.retrieveCandidate(controlConfigurationValueSet.specializationKey);
expect(true).toEqual(true);
});
});
So in my testsetup file I had
jest.useFakeTimers();
This caused my async functions to never return anything and caused my test to be in a frozen state

Jest, 2 describe make timeOut in test with 2 beforeEach mockClear()

I have a test file like this:
const axios = require('axios')
const Vacations = require('../../soap/vacationsSoap')
jest.mock('axios')
describe('describe a', () => {
beforeEach(() => { axios.default.post.mockClear() })
it('some it a.1', () => {
expect(Vacations.getReadXML()).toBe('something')
})
})
describe('describe b', () => {
beforeEach(() => { axios.default.post.mockClear() })
it('some it b.1', async () => {
axios.default.post.mockResolvedValueOnce({ data: 'sth' })
const client = new Vacations()
await expect(client.save()).rejects.toThrow('some error')
})
})
When I run the test, it gets hangout until I get a timeOut error.
But, if I remove the:
beforeEach(() => { axios.default.post.mockClear() })
from the second "describe" it works ok.
I copy pasted the while body from the describe 1 into 2, that's why I thought that it would works with the beforeEach. Why is happening that?

Why does my node.js HTTP requests fail when my server is initialized by my test suite?

I have been trying to write a test suite to my node.js API project and one of their requirements is to control when the server starts and stops. For that, I wrote this code below with two functions: initializeWebServer and stopWebServer.
express.js
const initializeWebServer = () => {
return new Promise((resolve, reject) => {
app = express();
/* some middlewares */
app.use('/', userRoutes);
httpServer = http.createServer(app);
httpServer.listen(3000, (err) => {
if (err) {
reject(err);
return;
}
resolve(app);
});
});
};
const stopWebServer = () => {
return new Promise((resolve, reject) => {
httpServer.close(() => { resolve(); });
});
};
Using mocha to run my tests, I choose to manage my server connection with before and after hooks, using async/await syntax.
user.spec.js
let axiosAPIClient;
before(async () => {
await initializeWebServer();
const axiosConfig = {
baseURL: `http://localhost:3000`,
validateStatus: () => true
};
axiosAPIClient = axios.create(axiosConfig);
});
after(async () => {
await stopWebServer();
});
describe('POST /api/user', () => {
it('when add a new user, then should get back approval with 200 response', async () => {
const userData = {
/* user props */
};
const response = await axiosAPIClient.post('/api/user', userData);
expect(response).to.containSubset({
status: 200,
data: { message: 'User signed up.' }
});
When axios (I tried fetch too) submit any HTTP request, mocha returns the following error: Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.. I tried to increase the timeout interval, but it didn't work.
If I execute my test suite without hooks, initializing my server with nodemon pointing to my index file with the same await initializeWebServer();, HTTP requests work as it should.
I really don't understand why this is happening, I think it's something with mocha.
I did some debug tests and figured out that I forgot to set up a db connection when I run my test suite, including in before and after hooks. It was a simple mistake.
user.spec.js
let axiosAPIClient;
before(async () => {
await initializeWebServer();
db.setUp();
/* axios config etc. */
after(async () => {
await stopWebServer();
await db.closeConnection();
});

Jest not close()ing expressjs server when run on AWS Codebuild

When I run locally jest exits fine, but when run on codebuild jest will not exit and gives this error:
Jest has detected the following 1 open handle potentially keeping Jest from exiting:
● TCPSERVERWRAP
13 | routes(app)
14 |
> 15 | app.listen(port, async err => {
Both these variants work locally but not on codebuild:
afterAll((done) => {
if (app) {
app.close(done);
}
})
afterAll(async () => {
if (app) {
await app.close()
}
})
Using process.exit(0) does not help
Ok, bit of an amateur error. When I wrapped the app.close() call it turned out that it was undefined. I was trying to run close on the express request object. I had to export the server object from where express was instantiated:
afterAll(async () => {
try {
await server.close()
} catch (error) {
console.error(error)
throw error;
}
})
const app = express()
routes(app)
const server = app.listen(port, async err => {
...
module.exports = app
module.exports.server = server

Fail mocha test in catch block of rejected promise

How to fail the test in catch block of promise rejection when making http call using axios?
Adding expectations, asserts, should expressions in catch block doesn't help.
The test is passing.
I's run using .\node_modules\.bin\mocha
let chai = require('chai');
var expect = chai.expect;
var axios = require('axios')
var instance = axios.create({})
describe('test', () => {
context('test', () => {
it('should succeed', () => {
let url = 'url'
instance.get(url)
.then(function(response) {
expect(response.data).not.to.be.null
} )
.catch(function(err) {
console.error(err.data)
// should fail the test
})
})
})
})
If You want to verify my suggestions, replace url value with valid url (ex: https://google.com)
You can try several ways:
1) Using assert.fail()
const axios = require('axios');
const { assert, expect } = require('chai');
const instance = axios.create({})
describe('test', () => {
context('test', () => {
it('should succeed', () => {
let url = 'abc'
return instance.get(url)
.then((res) => {
expect(res.data).not.to.be.null;
})
.catch((err) => {
assert.fail('expected', 'actual', err);
});
});
});
});
2) Using done() with error object
const axios = require('axios');
const { expect } = require('chai');
const instance = axios.create({})
describe('test', () => {
context('test', () => {
it('should succeed', (done) => {
let url = 'abc'
instance.get(url)
.then((res) => {
expect(res.data).not.to.be.null;
done();
})
.catch((err) => {
done(err);
});
});
});
});
3) Simply just throw an error :)
const axios = require('axios');
const { expect } = require('chai');
const instance = axios.create({})
describe('test', () => {
context('test', () => {
it('should succeed', () => {
let url = 'abc'
return instance.get(url)
.then((res) => {
expect(res.data).not.to.be.null;
})
.catch((err) => {
throw err;
});
});
});
})
If You want to check if that method fails at all and You expect this, go that way (it requires chai-as-promised package):
const axios = require('axios');
const chai = require('chai');
chai.use(require('chai-as-promised'));
const instance = axios.create({})
describe('test', () => {
context('test', () => {
it('should succeed', () => {
let url = 'abc'
return chai.expect(instance.get(url)).to.be.rejected;
});
});
});

Resources