- Introduction
- Installation and Usage
- Additional Configuration
- Advanced Practice
- More Practices
- References
Introduction
Jest is a delightful JavaScript Testing Framework with a focus on simplicity. It works with projects using: Babel, TypeScript, Node, React, Angular, Vue and more.
- Jest is easy to configure.
- Jest provides snapshots which make tests keep track of large objects with ease.
- Tests are running in parallel.
- Jest can generate code coverage.
- Jest uses a custom resolver for imports in tests, making it simple to mock any object outside of test’s scope.
Installation and Usage
Install Jest using npm
or yarn
:
npm install --save-dev jest
yarn add --dev jest
After installing, update the configuration package.json
:
{
"scripts": {
"test": "jest"
}
}
If jest
is not in path, try with "test": "node_modules/.bin/jest"
.
Assuming that there is a js file sum.js
with a function:
function sum(a, b) {
return a + b;
}
module.exports = sum;
Then create a unit test file sum.test.js
for the function:
const sum = require('./sum');
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});
Finally, run yarn test
or npm run test
and Jest will print this message:
PASS ./sum.test.js
✓ adds 1 + 2 to equal 3 (5ms)
Additional Configuration
Using Babel
In order to use Babel, install the required dependencies:
yarn add --dev babel-jest @babel/core @babel/preset-env
Create a configuration file babel.config.js
in the root of the project to configure Babel:
module.exports = {
presets: [['@babel/preset-env', {targets: {node: 'current'}}]],
};
Using TypeScript
Jest supports TypeScript via Babel. Install the required dependency after installing Babel:
yarn add --dev @babel/preset-typescript
Add one preset in the array of presents: '@babel/preset-typescript'
.
Advanced Practice
Using Matchers
Jest uses matchers to test values in different ways with expect
.
- With exact equality:
expect(expression).toBe(value)
,expect(expression).toEqual(value)
,expect(expression).not.toBe(value)
. - With boolean:
toBeNull
matches onlynull
toBeUndefined
matches onlyundefined
toBeDefined
is the opposite oftoBeUndefined
toBeTruthy
matches anything that an if statement treats as truetoBeFalsy
matches anything that an if statement treats as false
- Comparing numbers:
toBeGreaterThan
,toBeGreaterThanOrEqual
,toBeLessThan
,toBeLessThanOrEqual
. - Checking strings against regular expressions with
toMatch
. - Checking an array or a collection:
toContain
. - Testing exception:
toThrow
.
Setup and Teardown
Jest provides some functions to deal with some work before or after tests including scoping. An example with full structure:
beforeAll(() => console.log('0 - beforeAll'));
afterAll(() => console.log('0- afterAll'));
beforeEach(() => console.log('0 - beforeEach'));
afterEach(() => console.log('0 - afterEach'));
test('test 0', () => console.log('0 - test'));
describe('Scoped / Nested block 1', () => {
beforeAll(() => console.log('1 - beforeAll'));
afterAll(() => console.log('1 - afterAll'));
beforeEach(() => console.log('1 - beforeEach'));
afterEach(() => console.log('1 - afterEach'));
test('test 1', () => console.log('1 - test'));
});
describe('Scoped / Nested block 2', () => {
beforeAll(() => console.log('2 - beforeAll'));
afterAll(() => console.log('2 - afterAll'));
beforeEach(() => console.log('2 - beforeEach'));
afterEach(() => console.log('2 - afterEach'));
test('test 2', () => console.log('2 - test'));
});
Mock Functions
Mock functions allow to test the links between code by erasing the actual implementation of a function, capturing calls to the function (and the parameters passed in those calls), capturing instances of constructor functions when instantiated with new, and allowing test-time configuration of return values.
There are two ways to mock functions: Either by creating a mock function to use in test code, or writing a manual mock to override a module dependency.
It can be used to inject test values:
const myMock = jest.fn();
console.log(myMock());
// > undefined
myMock.mockReturnValueOnce(10).mockReturnValueOnce('x').mockReturnValue(true);
console.log(myMock(), myMock(), myMock(), myMock());
// > 10, 'x', true, true
It can also mock modules:
...
jest.mock('moduleName');
...
moduleName.funcName.mockResolvedValue(value);
...
UI Components Testing
There are many JavaScript libraries or frameworks about UI components such React, Angular and VueJS. Jest also supports testing with these libraries.
For example, add Jest into an existing React project.
A dependency is required:
yarn add --dev react-test-renderer
And, modify the babel configuration file babel.config.js
:
module.exports = {
presets: ['@babel/preset-env', '@babel/preset-react'],
};
Create a test for a React component:
import React from 'react';
import renderer from 'react-test-renderer';
import MyReactComponent from './MyReactComponent';
test('MyReactComponent works', () => {
const component = renderer.create(
<MyReactComponent />,
);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
// manually trigger the callback
tree.props.onMouseEnter();
// re-rendering
tree = component.toJSON();
expect(tree).toMatchSnapshot();
// manually trigger the callback
tree.props.onMouseLeave();
// re-rendering
tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
Snapshot Testing
Snapshot tests are a very useful tool whenever you want to make sure your UI does not change unexpectedly.
A typical snapshot test case renders a UI component, takes a snapshot, then compares it to a reference snapshot file stored alongside the test. The test will fail if the two snapshots do not match: either the change is unexpected, or the reference snapshot needs to be updated to the new version of the UI component.
The usage of it:
expect(expression).toMatchSnapshot();
A snapshot file testFilename.snap
will be generated if the test is run at the first time. And it needs to be updated if the component is changed.
DOM Testing
If you don’t want to use snapshot, you can try to use DOM to deal with the expected output. Some libraries such as Testing Library, Enzyme and React’s TestUtils can be used to manipulate the rendered components.
Install testing=library for React test:
yarn add --dev @testing-library/react
A test example with testing-library:
import React from 'react';
import {cleanup, fireEvent, render} from '@testing-library/react';
import MyReactComponent from './MyReactComponent';
// unmount and cleanup DOM after the test is finished.
afterEach(cleanup);
test('MyReactComponent works', () => {
const {queryByLabelText, getByLabelText} = render(
<MyReactComponent />,
);
expect(queryByLabelText(/off/i)).toBeTruthy();
fireEvent.click(getByLabelText(/off/i));
expect(queryByLabelText(/on/i)).toBeTruthy();
});
More Practices
There are other configuration or plugins which are helpful.
Configure coverage, including coverage files, reporter formats and threshold:
{
"jest": {
"preset": "...",
"collectCoverageFrom": [
"components/**/*.js",
"!hooks/*.js",
"!locales/*.js"
],
"coverageReporters": [
"text",
"html"
],
"coverageThreshold": {
"global": {
"branches": 50,
"functions": 50,
"lines": 50,
"statements": 50
}
}
}
}
The transformIgnorePatterns
option can be used to specify which files shall be transformed by Babel. Many react-native npm modules unfortunately don’t pre-compile their source code before publishing.
{
"jest": {
"preset": "...",
"transformIgnorePatterns": [
"node_modules/(?!(react-native|my-project|react-native-button)/)",
"/node_modules/otherExpression"
]
}
}
Add Jest for expo project:
yarn add --dev jest-expo
Configure preset:
"jest": { "preset": "jest-expo" }
// or
"jest": { "preset": "jest-expo/universal" }
Jest even supports to mock implementation and create custom matchers.
If some code uses a method which JSDOM (the DOM implementation used by Jest) hasn’t implemented yet, testing it is not easily possible. This is e.g. the case with window.matchMedia(). Jest returns TypeError: window.matchMedia is not a function and doesn’t properly execute the test.
In this case, mocking matchMedia
in the test file should solve the issue:
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation(query => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(), // deprecated
removeListener: jest.fn(), // deprecated
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});
References
blog comments powered by Disqus