How to pass in the result of an async call to shared functions in Jest
Problem
I am testing API endpoints and have some tests which are the same for every endpoint. Instead of copy and paste, I would like to share it in a reusable function. I also want a statement to be printed to the console (via test
) for every function, so I don't want to combine the logic.
Code
Here is the function which should be shared:
export function basicTests(res) {
test("should respond with a 200 status code", () => {
expect(res.statusCode).toBe(200);
});
test("should specify json in the content type header", () => {
expect(res.headers["content-type"]).toEqual(expect.stringContaining("json"));
});
test("should return an ok property", () => {
expect(res.body.ok).toBeDefined();
});
test("should return a description property", () => {
expect(res.body.description).toBeDefined();
});
}
And here is how I would call the tests in another file:
let res = {}
beforeAll(async () => {
const response = await request(app)
.get("/some/api/check")
.send();
res = { ...response };
});
describe("GET /some/api/route", () => {
basicTests(res);
// ... more tests
});
What does not work
Currently when I pass in res
, it is always an empty object. So even that beforeAll
runs before the tests, the variable seems to be passed earlier and so gets not updated when the Promise resolves.
How can I fix this?
Solution 1:
The test cases are defined synchronously. That means the res
is empty object {}
when the basicTests
function executes.
beforeAll(fn, timeout)
:
Runs a function before any of the tests in this file run. If the function returns a promise or is a generator, Jest waits for that promise to resolve before running tests.
That means the function passed in beforeAll
will be run before the test cases functions are executed. Since the function returns a promise, jest waits for that promise.
The main difference is declaration and runtime.
One solution is to put the beforeAll
things into basicTests
function. E.g.
basicTests.ts
:
import request from 'supertest';
export function basicTests(app, route) {
describe('basic test suites', () => {
let res;
beforeAll(async () => {
const response = await request(app).get(route);
res = { ...response };
});
test('should respond with a 200 status code', () => {
expect(res.statusCode).toBe(200);
});
test('should specify json in the content type header', () => {
expect(res.headers['content-type']).toEqual(expect.stringContaining('json'));
});
test('should return an ok property', () => {
expect(res.body.ok).toBeDefined();
});
test('should return a description property', () => {
expect(res.body.description).toBeDefined();
});
});
}
app.ts
:
import express from 'express';
const app = express();
app.get('/some/api/check', (req, res) => {
res.json({ ok: true, description: null, data: 'a' });
});
export { app };
app.test.ts
:
import { basicTests } from './basicTests';
import request from 'supertest';
import { app } from './app';
describe('GET /some/api/route', () => {
basicTests(app, '/some/api/check');
let res;
beforeAll(async () => {
const response = await request(app).get('/some/api/check');
res = { ...response };
});
test('should return data', () => {
expect(res.body.data).toBe('a');
});
});
test result:
PASS stackoverflow/70702998/app.test.ts (8.972 s)
GET /some/api/route
✓ should return data
basic test suites
✓ should respond with a 200 status code (2 ms)
✓ should specify json in the content type header
✓ should return an ok property
✓ should return a description property
Test Suites: 1 passed, 1 total
Tests: 5 passed, 5 total
Snapshots: 0 total
Time: 9.042 s