Jest clean up after all tests have run - node.js

Is it possible in Jest to run cleanup or teardown tasks that run after all other tests have completed? Similar to how setupFiles allows one to set up tasks after before any test has run. Bonus points if this can also run regardless if the test had any errors.
Putting afterAll(() => {}) at the top level of a file (outside any describe function) appears only to run after tests from that particular file have finished.
The use case is I have many test files that will create users in a a development database, and I don't want to make each test file responsible for cleaning up and removing the user afterwards. Errors can also happen while writing tests, so if the cleanup happens regardless of errors that would be preferable.

There's a sibling hook to setupFiles that will too fire before every test suite but right after your test runner (by default Jasmine2) has initialised global environment.
It's called setupFilesAfterEnv. Use it like this:
{
"setupFilesAfterEnv": ["<rootDir>/setup.js"]
}
Example setup.js:
beforeAll(() => console.log('beforeAll'));
afterAll(() => console.log('afterAll'));
setup.js doesn't need to export anything. It will be executed before every test suite (every test file). Because test runner is already initialised, global functions like beforeAll and afterAll are in the scope just like in your regular test file so you can call them as you like.

In jest.config.js:
module.exports = {
// ...
setupFilesAfterEnv: [
"./test/setup.js",
// can have more setup files here
],
}
In ./test/setup.js:
afterAll(() => { // or: afterAll(async () => { }); to support await calls
// Cleanup logic
});
Note:
I am using Jest 24.8
Reference:
setupFilesAfterEnv

To do some tasks after all test suites finish, use globalTeardown. Example:
In package.json:
{
"jest": {
"globalTeardown": "<rootDir>/teardownJest.js"
},
}
In teardownJest.js:
const teardown = async () => {
console.log('called after all test suites');
}
module.exports = teardown;
Keep in mind that jest imports every module from scratch for each test suit and teardown file. From official documentation:
By default, each test file gets its own independent module registry
So, you cannot share the same DB module's instance for each test suite or teardown file. Therefore, If you wanted to close db connection after all test suits, this method would not work

There looks like there is a feature called a reporter that just does exactly this:

Related

Is there a race condition using msw with jest?

I'm worried about race conditions w.r.t. MSW when multiple Jest test files run concurrently. Is that an issue?
The MSW getting started guide suggests the following code in setupTests.js:
// Reset any request handlers that we may add during the tests,
// so they don't affect other tests.
afterEach(() => server.resetHandlers())
But what if two tests run concurrently, and each starts with something like:
server.use(rest.get('https://api.backend.dev/user', (req, res, ctx) => {
return res(ctx.json({ firstName: 'Paul' }))
})),
or
server.use(rest.get('https://api.backend.dev/user', (req, res, ctx) => {
return res(ctx.json({ firstName: 'Ringo' }))
})),
Couldn't the handler set up in one test be called by the other test (or vice-versa)?
Do I need to use --runinband?
PS: Maybe I'm misunderstanding Jest because it seems like the "mock dom" implemented by jsdom would have the same issues with concurrently running tests.
I've at least convinced myself there's no race condition based on two facts:
Each Jest test file runs in its own subprocess. (This is mentioned in #5623 among other places.). So each test file should have its own server instance.
MSW on Node just monkey-patches functions like fetch() and XMLHttpRequest, rather than actually starting a web server. This is mentioned in #407. So, the monkey-patching should be isolated to each process running each test file.

BeforeEach step is repeated with cy.session using cypress-cucumber-preprocessor

I have a Cypress project where I use the Cypress session API to maintain a session throughout features.
Now I try switching from the deprecated Klaveness Cypress Cucumber Preprocessor to the replacement, Badeball's Cypress Cucumber Preprocessor. But I am running into an issue; the beforeEach() step where my authentication takes place gets repeated several times before the tests start. Eventually, Cypress "snaps out of it" and starts running the actual tests - but obviously this is very resource and time intensive, something is going wrong.
My setup:
Dependencies:
"cypress": "^9.6.1",
"#badeball/cypress-cucumber-preprocessor": "^9.1.3",
index.ts:
beforeEach(() => {
let isAuthInitialized = false;
function spyOnAuthInitialized(window: Window) {
window.addEventListener('react:authIsInitialized', () => {
isAuthInitialized = true;
});
}
login();
cy.visit('/', { onBeforeLoad: spyOnAuthInitialized });
cy.waitUntil(() => isAuthInitialized, { timeout: 30000 });
});
login() function:
export function login() {
cy.session('auth', () => {
cy.authenticate();
});
}
As far as I can see, I follow the docs for cy.session almost literally.
My authenticate command has only application specific steps, it does include a cy.visit('/') - after which my application is redirected to a login service (different domain) and then continues.
The problem
cy.session works OK, it creates a session on the first try - then each subsequent time it logs a succesful restore of a valid session. But this happens a number of times, it seems to get stuck in a loop.
Screenshot:
It looks to me like cy.visit() is somehow triggering the beforeEach() again. Perhaps clearing some session data (localstorage?) that causes my authentication redirect to happen again - or somehow makes Cypress think the test starts fresh. But of course beforeEach() should only happen once per feature.
I am looking at a diff of my code changes, and the only difference except the preprocessor change is:
my .cypress-cucumber-preprocessorrc.json (which I set up according to the docs
typing changes, this preprocessor is stricter about typings
plugins/index.ts file, also set up according to the docs
Am I looking at a bug in the preprocessor? Did I make a mistake? Or something else?
There are two aspects of Cypress + Cucumber with preprocessor that make this potentially confusing
Cypress >10 "Run all specs" behaviour
As demonstrated in Gleb Bahmutov PhD's great blog post, if you don't configure Cypress to do otherwise, running all specs runs each hook before each test. His proposed solution is to not use the "run all specs" button, which I find excessive - because there are ways around this; see below for a working solution with the Cucumber preprocessor.
Note: as of Cypress 10, "run all specs" is no longer supported (for reasons related to this unclarity).
Cucumber preprocessor config
The Cypress Cucumber preprocessor recommends to not use the config option nonGlobalStepDefinitions, but instead configure specific paths like (source):
"stepDefinitions": [
"cypress/integration/[filepath]/**/*.{js,ts}",
"cypress/integration/[filepath].{js,ts}",
"cypress/support/step_definitions/**/*.{js,ts}",
]
}
What it doesn't explicitly state though, is that the file which includes your hooks (in my case index.ts) should be excluded from these paths if you don't want them to run for each test! I could see how one might think this is obvious, but it's easy to accidentally include your hooks' file in this filepath config.
TLDR: If I exclude my index.ts file which includes my hooks from my stepDefinitions config, I can use "run all specs" as intended - with beforeEach() running only once before each test.

"top level" test in jest

While writing integration tests in jest I would like to reproduce the same behaviour I have achieved in mocha by:
mocha -r ts-node/register tests/integration/topLevelTest.test.ts 'tests/integration/**/*.test.ts'.
topLevelTest.test.ts :
let importantVariable;
describe("should do something with my variable", () => {
importantVariable = returnSomethingImportant();
it("should important variable exists", () => {
should.exist(importantVariable)
})
})
after(() => {
importantVariable.cleanUp()
})
Behaviour was simple: firstly topLevelTest executed describe, then other test suites executed themselves, and in the end after within topLevelTest were executed.
In my attempt of rewriting it to jest I wrote something very similar. Only difference is I used afterAll instead of after. The result is: firstly topLevelTest executed describe, then afterAll, and then other test suites. Is it possible to make afterAll run after other test suites?
This is what setup files are for, more specifically setupFilesAfterEnv because Jest environment is already initialized there with globals being available.
Top-level afterAll that wasn't grouped with describe applies to all tests within current test suite. Since Jest tests run in parallel (unless runInBand option was specified) in different threads, it obviously won't affect other test suites.
In case tests need to not proceed if a setup failed and data from setup needs to not be, globalSetup and globalTeardown configuration options should be used for that. This is not a test but the main difference is that describe and separate test blocks are unavailable. Global expect is not available but can be imported, this results in meaningful errors in case a setup fails:
// setup.js
let expect = require('expect');
module.exports = async () => {
let server = ...;
expect(server)...;
global.__MYSERVER__ = server;
};
// teardown.js
module.exports = async function () {
// close __MYSERVER__
};
Since global setup and teardown run in parent process, __MYSERVER__ cannot be accessed in tests.

Is there any way to add callbacks to Jest when all tests succeed or fail?

I'd like to run a callback when all the tests in a describe block pass (or fail), is there some hook or something in the Jest API to do this? I could not find anything applicable in the docs.
I'm making several API requests to collect data in order to compare it to data in a CSV file, in order to diff the contents. When the tests have all passed, I would like to save all the API responses in a file, therefore I need some sort of 'all tests passed' callback
You can run jest programmatically. Note that this approach is "hack" because there is no official support for running jest like this.
see: https://medium.com/web-developers-path/how-to-run-jest-programmatically-in-node-js-jest-javascript-api-492a8bc250de
There is afterAll that is aware of describe but runs regardless of test results. It can be used as a part of function to aggregate data from tests:
let responses;
testAndSaveResponses((name, fn) => {
if (!responses) {
responses = [];
} else {
afterAll(async () => {
if (!responses.includes(null)) {
// no errors, proceed with response processing
}
});
}
test(name, async () => {
try {
responses.push(await fn());
} catch (err) {
responses.push(null);
throw err;
}
});
});
It's supposed to be used instead of Jest test and be enhanced to support multiple describe scopes.
There is custom environment. Circus runner allows to hook test events, finish_describe_definition in particular. It is applied to all tests, unaware of custom data (e.g. responses that need to be saved) and should interact with them through global variables.
There is custom reporter, it receives a list of passed and failed tests. It is applied to all tests, unaware of custom data defined in tests and doesn't have access to globals from test scope so cannot be used to collect responses.

How to run 'After' code on Mocha only on specific tests?

Edit: This question was answered, but I have another, similar question which I didn't want to open a new thread for.
I'm using Mocha and Chai to test my project.
As part of my code, I create a new user and save him in our DB (so the test user can perform various methods on our app).
Generally, after every test I would like to run a code block that deletes the user from the DB, which I did using the "AfterEach" hook.
My problem is that I have 1 test (might be more in the future) which doesn't create a user (e.g, 'try to login without signing up'), so my AfterEach code receives an error (can't delete something that doesn't exist).
Does Mocha supply a way to disable the 'AfterEach' on some tests? Or some other solution to my problem.
Edit: Added question: my AfterEach hook involves an async method which returns a promise. On the Mocha documentation I only saw an example for async hooks that work with callbacks. How am I supposed to use an afterEach hook that returns a promise
You can nest describe blocks, so you can group user interaction tests and also group the "with user" and "without user" tests:
describe('user interaction', () => {
describe('with user in database', () => {
// these will run only for the tests in this `describe` block:
beforeEach(() => createUser(...));
afterEach (() => deleteUser(...));
it(...);
});
describe('without user in database', () => {
it(...);
});
});

Resources