Jest cleanup after test case failed
Asked Answered
F

3

7

What is a good and clean way to cleanup after a test case failed? For a lot of test cases, I first create a clean database environment, which needs to be cleaned up after a test case finishes.

test('some test', async () => {
  const { sheetService, cleanup } = await makeUniqueTestSheetService();

  // do tests with expect()

  await cleanup();
});

Problem is: If one of the expects fails, cleanup() is not invoked and thus the database environment is not cleaned up and jest complains Jest did not exit one second after the test run has completed. because the connection is not closed.

My current workaround looks like this, but it doesn't feel good and clean to push the cleanup hooks to an array which than is handled in the afterAll event.

const cleanUpHooks: (() => Promise<void>)[] = [];

afterAll(async () => {
  await Promise.all(cleanUpHooks.map(hook => hook()));
});

test('some test', async () => {
  const { sheetService, cleanup } = await makeUniqueTestSheetService();

  // do tests with expect()

  await cleanup();
});
Flophouse answered 12/2, 2019 at 11:24 Comment(7)
May be this will work if you are using mongo as your database npmjs.com/package/mongo-clean.Esposito
I usually delete and restore the database in afterEach execution for unit tests because I want them isolated to also been able to run them in parallel to decrease the time spent in the process. If you don't need that level of isolation, restoring the database in the afterAll execution it seems enough clean from my point of view.Selfimportant
@Selfimportant How do you access resources created in a test case from afterEach? In my case it would be the database connection.Flophouse
the best and safest way is to spin up DB for every test separately. This gives you 100% isolation.Kowalczyk
@MU That's exactly what I am doing at the moment. The question is, how can I safely cleanup the database and connection (that's what the cleanup function does), even if a test case fails ;)Flophouse
You should initialize db beforeEach so it does not matter if it fails or notKowalczyk
@MU But then I have to use shared variables in the beforeEach and the test cases for the database connection reference, which can lead to some unexpected fails when executed in parallel, right?Flophouse
U
8

Use try/finally block for such cases.

For example:

  it("some test case", async done => {
    try {
      expect(false).toEqual(true);
      console.log("abc");
      done();
    }finally{
      console.log("finally");
      // cleanup code below...
    }
  });

Above code would just execute the finally block (hence "finally" gets printed in console instead of "abc". Note that the catch block gives a timeout so just use finally.

This answer must be useful.

Unipolar answered 9/12, 2020 at 12:11 Comment(0)
S
1

What if you use afterEach()? it will execute after each test

test('some test', async () => {
  const { sheetService, cleanup } = await makeUniqueTestSheetService();

  // do tests with expect()

});

afterEach(async()=>{
    await cleanup();
});
Shericesheridan answered 24/7, 2019 at 12:28 Comment(2)
afterEach is good for cleaning up items created beforeEach test. What if you create specific objects in one test, and want to clean those up at the end of that test?Induline
Might not be a good practice. But you can use an if statement to check for test method name in afterEach.Shericesheridan
M
0

Building on top of rahulserver's answer, you can use create a wrapper function to avoir repeating try / finally syntax over and over:

async function withCleanup (fn, cleanup) {
  try {
    await fn();
  } finally {
    await cleanup();
  }
}

test('some test', async () => {
  const { sheetService, cleanup } = await makeUniqueTestSheetService();

  await withCleanup (async () => {
    // do tests with expect()
  });
});

You could also go one step further and wrap your setup logic as well, for example:

async function withService (fn) {
  const { sheetService, cleanup } = await makeUniqueTestSheetService();
  try {
    await fn(sheetService);
  } finally {
    await cleanup();
  }
}

test('some test', async () => {
  await withService (async (sheetService) => {
    // do tests with expect()
  });
});
Mellette answered 24/5 at 18:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.