Run ava test.before() just once for all tests - node.js

I would like to use test.before() to bootstrap my tests. The setup I have tried does not work:
// bootstrap.js
const test = require('ava')
test.before(t => {
// do this exactly once for all tests
})
module.exports = { test }
// test1.js
const { test } = require('../bootstrap')
test(t => { ... {)
AVA will run the before() function before each test file. I could make a check within the before call to check if it has been called but I'd like to find a cleaner process. I have tried using the require parameter with:
"ava": {
"require": [
"./test/run.js"
]
}
With:
// bootstrap,js
const test = require('ava')
module.exports = { test }
// run.js
const { test } = require('./bootstrap')
test.before(t => { })
// test1.js
const { test } = require('../bootstrap')
test(t => { ... {)
But that just breaks with worker.setRunner is not a function. Not sure what it expects there.

AVA runs each test file in its own process. test.before() should be used to set up fixtures that are used just by the process it's called in.
It sounds like you want to do setup that is reused across your test files / processes. Ideally that's avoided since you can end up creating hard-to-detect dependencies between the execution of different tests.
Still, if this is what you need then I'd suggest using a pretest npm script, which is run automatically when you do npm test.

In your package.json you could run a setup script first...
"scripts": {
"test": "node setup-test-database.js && ava '*.test.js'"
}
Then...
In that setup-test-database.js file, have it do all your bootstrappy needs, and save a test-config.json file with whatever you need to pass to the tests.
In each test you just need to add const config = require('./test-config.json'); and you'll have access to the data you need.

Related

Mock file in Jest and prevent getting loaded

I have a file that uses a webworker that I want to unit test. But because Jest is flaky on ESModules, it doesn't like import.meta.url.
I don't want to test this part, I need to test the functionality around it. But because it's still included in the build, js-test keeps tripping over having this included in the build.
I abstracted it into a separate JS file worker-loader.js:
const spawnWorker = () => {
return new Worker(new URL('./worker.ts', import.meta.url));
};
module.exports = { spawnSqlJsWorker};
Which is use with a require in the main source code.
I tried adding a mocks directory with an empty mock:
const spawnWorker = () => {
return null;
};
module.exports = { spawnSqlJsWorker};
And ignore this file from jest in jest.config.js:
transformIgnorePatterns: ['worker-loader.js'],
But the loader keeps wanting to include this file. Is there a way in jest to completely ignore a file and mock the complete contents of it?

Jest Run All Tests(include only/skip) in CI

While in development we occasionally use skip or only to debug a particular test or test suit. Accidentally, we might forget to revert the cases and push the code for PR. I am looking for a way to detect or automatically run all tests even for skip and only tests in our CI pipeline(using Github action). It can be in either case as follow.
Fail the test when there are skip or only tests.
Run all tests even for skip and only.
Very much appreciate any help.
I came up with a solution for the second part of the question about running all tests even for skip and only. I don't think it's elegant solution, but it works and it's easy to implement.
First of all you need to change test runner to jest-circus if you work with jest bellow 27.x version. We need it so our custom test environment will use handleTestEvent function to watch for setup events. To do so, install jest-circus with npm i jest-circus and then in your jest.config.js set testRunner property:
//jest.config.js
module.exports = {
testRunner: 'jest-circus/runner',
...
}
From Jest 27.0 they changed default test runner to jest-circus so you can skip this step if you have this or higher version.
Then you have to write custom test environment. I suggest to write it based on jsdom so for example we also have access to window object in tests and etc. To do so run in terminal npm i jest-environment-jsdom and then create custom environment like so:
//custom-jsdom-environment.js
const JsDomEnvironment = require('jest-environment-jsdom')
class CustomJsDomEnvironment extends JsDomEnvironment {
async handleTestEvent(event, state) {
if(process.env.IS_CI === 'true' && event.name === 'setup') {
this.global.describe.only = this.global.describe
this.global.describe.skip = this.global.describe
this.global.fdescribe = this.global.describe
this.global.xdescribe = this.global.describe
this.global.it.only = this.global.it
this.global.it.skip = this.global.it
this.global.fit = this.global.it
this.global.xit = this.global.it
this.global.test.only = this.global.test
this.global.test.skip = this.global.test
this.global.ftest = this.global.test
this.global.xtest = this.global.test
}
}
}
module.exports = CustomJsDomEnvironment
And inform jest to properly use it:
//jest.config.js
module.exports = {
testRunner: 'jest-circus/runner',
testEnvironment: 'path/to/custom/jsdom/environment.js',
...
}
Then you just have to setup custom environment value IS_CI in your CI pipeline and from now on all your skipped tests will run.
Also in custom test environment you could watch for skipped test and throw an error when your runner find skip/only. Unfortunately throwing an error in this place won't fail a test. You would need to find a way to fail a test outside of a test.
//custom-jsdom-environment.js
const JsDomEnvironment = require('jest-environment-jsdom')
const path = require('path')
class CustomJsDomEnvironment extends JsDomEnvironment {
constructor(config, context) {
super(config, context)
const testPath = context.testPath
this.testFile = path.basename(testPath)
}
async handleTestEvent(event, state) {
if(process.env.IS_CI === 'true' && event.name === 'add_test') {
if(event.mode === 'skip' || event.mode === 'only') {
const msg = `Run ${event.mode} test: '${event.testName}' in ${this.testFile}`
throw new Error(msg)
}
}
}
}
module.exports = CustomJsDomEnvironment

Use jest.run() or jest.runCLI() to run all tests or ways to run jest programmatically

How do I use jest.run() or jest.runCLI() to run all tests programmatically? What am I suppose to feed as an argument?
I tried to find documentation regarding them but fail.
And if the above functions don't work, what am I supposed to call if I want to run jest programmatically?
Jest is not supposed to be run programmatically. Maybe it will in the future.
Try to run following:
const jest = require("jest");
const options = {
projects: [__dirname],
silent: true,
};
jest
.runCLI(options, options.projects)
.then((success) => {
console.log(success);
})
.catch((failure) => {
console.error(failure);
});
As success in then callback an object will be passed, containing globalConfig and results keys. Have a look on them, maybe it will help you.
From what I have experienced so far, utilizing run() requires to you define a static config and then pass arguments to Jest much like you would normally using the Jest CLI.
Utilizing runCLI() allows you to dynamically create a config and provide it to Jest.
I opted for the former just because I wanted to only expose a few of the Jest CLI options for a global configuration:
import jest from "jest";
import { configPaths } from "../_paths";
import { Logger } from "../_utils";
process.env.BABEL_ENV = "test";
process.env.NODE_ENV = "test";
const defaultArgs = ["--config", configPaths.jestConfig];
const log = new Logger();
const resolveTestArgs = async args => {
let resolvedArgs = [];
if (args.file || args.f) {
return [args.file || args.f, ...defaultArgs];
}
// updates the snapshots
if (args.update || args.u) {
resolvedArgs = [...resolvedArgs, "--updateSnapshot"];
}
// tests the coverage
if (args.coverage || args.cov) {
resolvedArgs = [...resolvedArgs, "--coverage"];
}
// runs the watcher
if (args.watch || args.w) {
resolvedArgs = [...resolvedArgs, "--watch"];
}
// ci arg to update default snapshot feature
if (args.ci) {
resolvedArgs = [...resolvedArgs, "--ci"];
}
// tests only tests that have changed
if (args.changed || args.ch) {
resolvedArgs = [...resolvedArgs, "--onlyChanged"];
}
return [...defaultArgs, ...resolvedArgs];
};
export const test = async cliArgs => {
try {
const jestArgs = await resolveTestArgs(cliArgs);
jest.run(jestArgs);
} catch (error) {
log.error(error);
process.exit(1);
}
};
Here's example from my post on How to run Jest programmatically in node.js (Jest JavaScript API).
This time using TypeScript.
Install the dependencies
npm i -S jest-cli
npm i -D #types/jest-cli #types/jest
Make a call
import {runCLI} from 'jest-cli';
import ProjectConfig = jest.ProjectConfig;
const projectRootPath = '/path/to/project/root';
// Add any Jest configuration options here
const jestConfig: ProjectConfig = {
roots: ['./dist/tests'],
testRegex: '\\.spec\\.js$'
};
// Run the Jest asynchronously
const result = await runCLI(jestConfig as any, [projectRootPath]);
// Analyze the results
// (see typings for result format)
if (result.results.success) {
console.log(`Tests completed`);
} else {
console.error(`Tests failed`);
}
Also, regarding #PeterDanis answer, I'm not sure Jest will reject the promise in case of a failed tests. In my experience it will resovle with result.results.success === false.
If all your configs are in the jest.config.js, you can just code like this:
const jest = require('jest')
jest.run([])

Jest beforeAll() share between multiple test files

I have a Node.js project that I'm testing using Jest. I have several test files that have the same setup requirement. Previously, all these tests were in one file, so I just had a beforeAll(...) that performed the common setup. Now, with the tests split into multiple files, it seems like I have to copy/paste that beforeAll(...) code into each of the files. That seems inelegant - is there a better way to do this, ideally where I can just write my beforeAll(...)/setup logic once, and "require" it from multiple test files? Note that there are other tests in my test suite that don't require this setup functionality, so I don't want to make all my tests run this setup (just a particular subset of test files).
If you're using Jest >=20, you might want to look into creating a custom jest-environment for the tests that require this common setup. This would be a module that extends either jest-environment-node or jest-environment-jsdom, and implements async setup(), async teardown(), and async runScript() to do this setup work.
You can then add a #jest-environment my-custom-env directive to those files that require this setup.
See the Jest config docs for testEnvironment for details on how to set this up; there's a simple example there.
I am using a simple "test hooks" pattern for this:
// This function wraps beforeAll and afterAll into a single RAII-like call.
// That makes the describe code further down easier to read and makes
// sure you don't forget the afterAll part. Can easily be shared between tests.
function useFakeServer() {
let server;
beforeAll(() => server = sinon.fakeServer.create());
afterAll(() => server.restore());
return () => server;
}
describe('Some scenario', () => {
const getServer = useFakeServer();
it('accesses the server', () => {
const server = getServer();
// Test as you normally would..
expect(server.requests[0]. /* ... */);
});
});
If you need a script to run before all your test files, you can use globalSetup
This option allows the use of a custom global setup module which exports an async function that is triggered once before all test suites.
in your jest.config.js
//jest.config.js
module.exports = {
...
testTimeout: 20000,
globalSetup: "./setup.js"
};
then create a file named setup.js
// setup.js
module.exports = async () => {
console.log("I'll be called first before any test cases run");
//add in what you need to do here
};
Docs
You can move your beforeAll logic into one file and reference it in jest.config.js setupFilesAfterEnv section:
module.exports = {
...
setupFilesAfterEnv: ['<rootDir>/testHelper.ts'],
...
}
https://jestjs.io/docs/en/configuration#setupfilesafterenv-array
Create a function somewhere like so:
export function setupBeforeAndAfter(putParamsHereIfYouHaveAny) {
beforeAll(() => shared-before-all-code);
afterAll(() => shared-after-all-code);
beforeEach(() => shared-before-each-code);
afterEach(() => shared-after-each-code);
}
Then just call it wherever you would otherwise have manually written these functions:
describe('My test', () => {
setupBeforeAndAfter(putParamsHereIfYouHaveAny)
it('is amazing', () => {
// Stuff in setupBeforeAndAfter() will run before/after this test as appropriate
})
})

Code Coverage for Istanbul Wrong when using Sandbox in Nodeunit

I have written a bunch of tests using nodeunit to test my code. In doing so I wanted to mock out modules being required by the code under test. Instead of changing the code to make it more easily testable with mocks, inversion of control, when it wasn't needed, I instead used nodeunits sandbox function.
Example
var nodeunit = require("nodeunit");
export.MyTest = {
test1(test) {
var fakeGlobals = {
require: function(filename) {
if (filename == "CoolUtil.js") {
return { doit: function wasCool() { return true; } };
} else {
return require(filename);
}
}
};
var testSubject = nodeunit.utils.sandbox("ModuleUnderTest.js", fakeGlobals);
test.equals(42, testSubject.doSomethingCoolUsingCoolUtil(), "Worked");
test.done();
}
}
Istanbul is giving me the wrong coverage report numbers. I tried using the flag --post-require-hook which is said to be used with RequireJS, which I'm fine with switching to but haven't learned yet.
test/node_modules/.bin/istanbul cover --v --hook-run-in-context --root test/node_modules/.bin/nodeunit -- --reporter junit --output target/results/unit_tests test
Has anybody been successful with nodeunit, istanbul and using the sandbox feature in nodeunit?

Resources