I would like to run code before and after all the tests, not file wide, test wide. For example;
Before starting the e2e tests, I would like to run the server in test mode with the database. After all, I would like to flush my db and close the processes.
I don't know if it is possible but I also would like to have a global db variable to do tests. What I am currently doing is like this:
describe("Posts Module", () => {
let dbService: DatabaseService;
beforeAll(async () => {
dbService = new DatabaseService();
await dbService.init();
});
it("should give the posts", () => {
supertest(app)
.get("/posts")
.expect(200)
.then(async (response) => {
const dbPosts = await dbService.getPosts();
expect(response.body).toBeDefined();
expect(response.body.posts).toEqual(dbPosts);
});
});
afterAll(async () => {
await flushDb(dbService);
await dbService.close();
});
});
But what I actually want is initing this database service only once before all of the module tests (also starting the server, currently I start the server manually and run the tests afterwards).
I get this error with firstValueFrom when using NestJS + Axios HttpService.
Jest has detected the following 1 open handle potentially keeping Jest from exiting:
await firstValueFrom(this.httpService.get(url, { headers }));
But at the same time, jest completes successfully and my requests do not hang. What is the right thing to do in this situation?
Don't suggest --forceExit :)
UPD #1:
public async getData() {
const url = 'correct url here :) ';
const headers = {};
return await firstValueFrom(this.httpService.get(url, { headers }));
}
Test:
describe('Service', () => {
describe('Main', () => {
it('get data', async () => {
const result = await service.getData();
expect(result.data).toBeDefined();
});
});
});
Context
I have spiked a TCP Echo server and am trying to write integration tests for it. I'm familiar with testing but not asynchronously.
Desired Behaviour
I would like my tests to spy on logs to verify that the code is being executed. Any asynchronous code should be handled properly, but this is where my understanding falls through.
Problem
I am getting asynchronous errors:
Cannot log after tests are done. Did you forget to wait for something async in your test?
Attempted to log "Server Ready".
Attempted to log "Client Connected".
And finally a warning:
A worker process has failed to exit gracefully and has been force exited. This is likely caused by tests leaking due to improper teardown. Try running with --detectOpenHandles to find leaks.
Code
import * as net from 'net';
export const runServer = async () => {
console.log('Initialising...');
const port: number = 4567;
const server = net.createServer((socket: net.Socket) => {
socket.write('Ready for input:\n');
console.log('Client Connected');
socket.on('data', (data) => {
echo(data, socket);
server.close();
})
socket.on('end', () => {
console.log('Client Disconnected');
});
});
server.listen(port, () => {
console.log('Server Ready');
});
server.on('error', (err) => {
console.log(err);
});
function echo(data: Buffer, socket: net.Socket) {
console.log('Input received')
socket.write(data)
};
return server;
}
Test
More such tests will be added when these are working as intended.
import * as index from '../../src/index';
import * as process from 'child_process';
test('the server accepts a connection', async () => {
const consoleSpy = spyOn(console, 'log');
try {
const server = await index.runServer();
await consoleConnect();
await consoleEcho();
await consoleStop();
} catch (error) {
console.log(error);
}
expect(consoleSpy).toHaveBeenCalledWith('Initialising...');
expect(consoleSpy).toHaveBeenCalledWith('Client Connected');
expect(consoleSpy).toHaveBeenCalledTimes(2);
})
const consoleConnect = async () => {
process.exec("netcat localhost 4567");
}
const consoleEcho = async () => {
process.exec("Echo!");
}
const consoleStop = async () => {
process.exec("\^C");
}
My overall question is how do I manage the events in such a way that the tests are able to run without async-related errors?
You are not properly waiting for your child processes to finish. Calls to exec return a ChildProcess object as documented here. They execute asynchronously so you need to wait for them to finish using the event emitter api.
Ex from docs
ls.on('exit', (code) => {
console.log(`child process exited with code ${code}`);
});
To use async await you need to convert to using a promise. Something like
return new Promise((resolve, reject) => {
ls.on('exit', (code) => {
resolve(code);
});
// Handle errors or ignore them. Whatevs.
}
You are closing your server on the first data event. You probably don't want to do that. At least wait until the end event so you have read all the data.
socket.on('data', (data) => {
echo(data, socket);
server.close(); // Remove this line
})
I have my tests in TS with jest and supertest. Locally all works but inside the gitlab-runner (gitlab-ci) all falied because this:
TypeError: Cannot read property 'address' of undefined
This error is because don't find the server, but is there o_O
Only inside the runner apper this error.
An example of test:
beforeAll(async () => {
app = await Server.init();
});
afterAll(async () => {
await app.close();
});
test('it should return 200', async () => {
const res = await request(app).post(`${URL}/ok`).send(body);
expect(res.status).toEqual(200);
});
I'm export my listen server in the application. This tests is inside a describe.
Thanks
What I test: An express server endpoints
My goal: automate API tests in a single script
What I do: I launch the express server in a NodeJS child process and would like to wait for it to be launched before the test suite is run (frisby.js endpoints testing)
What isn't working as expected: Test suite is launched before Promise resolution
I rely on the wait-on package which server polls and resolves once the resource(s) is/are available.
const awaitServer = async () => {
await waitOn({
resources: [`http://localhost:${PORT}`],
interval: 1000,
}).then(() => {
console.log('Server is running, launching test suite now!');
});
};
This function is used in the startServer function:
const startServer = async () => {
console.log(`Launching server http://localhost:${PORT} ...`);
// npmRunScripts is a thin wrapper around child_process.exec to easily access node_modules/.bin like in package.json scripts
await npmRunScripts(
`cross-env PORT=${PORT} node -r ts-node/register -r dotenv/config src/index.ts dotenv_config_path=.env-tests`
);
await awaitServer();
}
And finally, I use this in something like
describe('Endpoints' () => {
beforeAll(startTestServer);
// describes and tests here ...
});
Anyway, when I launch jest the 'Server is running, launching test suite now!' console.log never shows up and the test suite fails (as the server isn't running already). Why does jest starts testing as awaitServer obviously hasn't resolved yet?
The npmRunScripts function works fine as the test server is up and running a short while after the tests have failed. For this question's sake, here's how npmRunScripts resolves:
// From https://humanwhocodes.com/blog/2016/03/mimicking-npm-script-in-node-js/
const { exec } = require('child_process');
const { delimiter, join } = require('path');
const env = { ...process.env };
const binPath = join(__dirname, '../..', 'node_modules', '.bin');
env.PATH = `${binPath}${delimiter}${env.PATH}`;
/**
* Executes a CLI command with `./node_modules/.bin` in the scope like you
* would use in the `scripts` sections of a `package.json`
* #param cmd The actual command
*/
const npmRunScripts = (cmd, resolveProcess = false) =>
new Promise((resolve, reject) => {
if (typeof cmd !== 'string') {
reject(
new TypeError(
`npmRunScripts Error: cmd is a "${typeof cmd}", "string" expected.`
)
);
return;
}
if (cmd === '') {
reject(
new Error(`npmRunScripts Error: No command provided (cmd is empty).`)
);
return;
}
const subProcess = exec(
cmd,
{ cwd: process.cwd(), env }
);
if (resolveProcess) {
resolve(subProcess);
} else {
const cleanUp = () => {
subProcess.stdout.removeAllListeners();
subProcess.stderr.removeAllListeners();
};
subProcess.stdout.on('data', (data) => {
resolve(data);
cleanUp();
});
subProcess.stderr.on('data', (data) => {
reject(data);
cleanUp();
});
}
});
module.exports = npmRunScripts;
I found the solution. After trying almost anything, I didn't realize jest had a timeout setup which defaults at 5 seconds. So I increased this timeout and the tests now wait for the server promise to resolve.
I simply added jest.setTimeout(3 * 60 * 1000); before the test suite.
In my case, it caused by the flaw of the beforeAll part. Make sure the beforeAll doesn't contain any uncaught exceptions, otherwise it will behaves that the testing started without waiting for beforeAll resolves.
After much digging I found a reason for why my beforeAll didn't seem to be running before my tests. This might be obvious to some, but it wasn't to me.
If you have code in your describe outside an it or other beforeX or afterY, and that code is dependent on any beforeX, you'll run into this problem.
The problem is that code in your describe is run before any beforeX. Therefore, that code won't have access to the dependencies that are resolved in any beforeX.
For example:
describe('Outer describe', () => {
let server;
beforeAll(async () => {
// Set up the server before all tests...
server = await setupServer();
});
describe('Inner describe', () => {
// The below line is run before the above beforeAll, so server doesn't exist here yet!
const queue = server.getQueue(); // Error! server.getQueue is not a function
it('Should use the queue', () => {
queue.getMessage(); // Test fails due to error above
});
});
});
To me this seems unexpected, considering that code is run in the describe callback, so my impression was that that callback would be run after all beforeX outside the current describe.
It also seems this behavior won't be changed any time soon: https://github.com/facebook/jest/issues/4097
In newer versions of jest (at least >1.3.1) you can pass a done function to your beforeAll function and call it after everything is done:
beforeAll(async (done) => {
await myAsyncFunc();
done();
})
it("Some test", async () => {
// Runs after beforeAll
})
More discussions here: https://github.com/facebook/jest/issues/1256