Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to share data between beforeAll / beforeEach and tests in Jest?

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:

  • https://github.com/facebook/jest/issues/3553
  • https://github.com/facebook/jest/issues/3673

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?

like image 932
peter Avatar asked Jan 30 '18 18:01

peter


People also ask

What is the difference between beforeAll and beforeEach?

Given the articles I found that BeforeAll is called once and only once. While the BeforeEach is called before each individual test.

Does beforeAll run beforeEach?

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.

What is beforeEach and afterEach in Jest?

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.

Does beforeEach run before describe?

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.


1 Answers

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

like image 116
lonewarrior556 Avatar answered Oct 01 '22 02:10

lonewarrior556