We use jest to test our API and have quite complex scenarios. We use the beforeAll
functions to set up general helper variables for each test and sometimes to set up tenant separation, in other cases we use the beforeEach
functions to set up tenant separation for the tests, with some default configuration for the test tenant, ...
For instance a test could like something like this (as you can see, we use TypeScript to write the tests, in case that would matter):
let apiClient: ApiClient;
let tenantId: string;
beforeAll(async () => {
apiClient = await getClientWithCredentials();
});
beforeEach(async () => {
tenantId = await createNewTestTenant();
});
describe('describing complex test scenario', () => {
it('should have some initial state', async () => {
await checkState(tenantId);
});
it('should have some state after performing op1', async () =>{
await op1(tenantId);
await checkStateAfterOp1(tenantId);
});
it('should have some state after performing op2', async () =>{
await op2(tenantId);
await checkStateAfterOp2(tenantId);
});
it('should have some state after performing op1 and op2', async () =>{
await op1(tenantId);
await op2(tenantId);
await checkStateAfterOp1AndOp2(tenantId);
});
it('the order of op1 and op2 should not matter', async () =>{
await op2(tenantId);
await op1(tenantId);
await checkStateAfterOp1AndOp2(tenantId);
});
});
describe('another similar complex scenario', () => {
// ... you see where this is going
});
The problem is, what's the best way to share those variables initialized by beforeAll
and beforeEach
? - the above test works if executed with the --runInBand
option, which "... runs all tests serially in the current process ..."
But it starts failing pretty randomly, when executed in parallel, mostly referring to tenantId
being undefined. Given that those tests are part of ~200 similar tests, serially all pass. In parallel it depends on the machine. The build agent which has 8 cores / 16 threads has only 50-60% passing tests. My colleagues with quad core CPUs have ~80% passing tests and for me with dual core CPU sometimes only 1-2 tests fail, other times ~10. So obviously it depends on the amount of parallelism.
I've found 2 GitHub issues where people were mentioning the possibility to use this
to share the context (which doesn't work anymore) or to encapsulate everything in describe
:
So I tried a very naive approach:
describe('tests', () => {
let apiClient: ApiClient;
let tenantId: string;
beforeAll(async () => {
apiClient = await getClientWithCredentials();
});
beforeEach(async () => {
tenantId = await createNewTestTenant();
});
describe('describing complex test scenario', () => {
it('should have some initial state', async () => {
await checkState(tenantId);
});
it('should have some state after performing op1', async () =>{
await op1(tenantId);
await checkStateAfterOp1(tenantId);
});
it('should have some state after performing op2', async () =>{
await op2(tenantId);
await checkStateAfterOp2(tenantId);
});
it('should have some state after performing op1 and op2', async () =>{
await op1(tenantId);
await op2(tenantId);
await checkStateAfterOp1AndOp2(tenantId);
});
it('the order of op1 and op2 should not matter', async () =>{
await op2(tenantId);
await op1(tenantId);
await checkStateAfterOp1AndOp2(tenantId);
});
});
describe('another similar complex scenario', () => {
// ... you see where this is going
});
});
But this didn't seem to have any effect. I'd really like to run the test in parallel, but I couldn't find anything regarding that in the documentation. Maybe I don't know what I should be looking for?
Given the articles I found that BeforeAll is called once and only once. While the BeforeEach is called before each individual test.
The other behaviour here to consider is that the nested-beforeAll actually runs before the top-beforeEach , meaning that if your nested-beforeAll was relying on the top-beforeEach to have run before the block was entered, you're going to be out of luck.
beforeEach and afterEach can handle asynchronous code in the same ways that tests can handle asynchronous code - they can either take a done parameter or return a promise.
If beforeEach is inside a describe block, it runs for each test in the describe block. If you only need to run some setup code once, before any tests run, use beforeAll instead.
would this work for you?
describe('tests', () => {
let apiClient: ApiClient;
let tenantIds: {id: string, used: boolean}[];
const findUnusedTenantId = () => {
const tenant = tenantIds.find(a => !a.used);
tenant.used = true;
return tenant.id
}
beforeAll(async () => {
apiClient = await getClientWithCredentials();
});
beforeEach(async () => {
const id = await createNewTestTenant();
tenantIds.push({id, used: false})
});
describe('describing complex test scenario', () => {
let tenantId: string
it('should have some initial state', async () => {
tenantId = fineUnusedTenantId();
await checkState(tenantId);
});
it('should have some state after performing op1', async () =>{
await op1(tenantId);
await checkStateAfterOp1(tenantId);
});
// ...
});
describe('next scenario', () => {
let tenantId: string
it('first test', async () => {
tenantId = fineUnusedTenantId();
await checkState(tenantId);
});
you'd prob want to have an afterAll hook to clean up the db
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With