Project Name | Stars | Downloads | Repos Using This | Packages Using This | Most Recent Commit | Total Releases | Latest Release | Open Issues | License | Language |
---|---|---|---|---|---|---|---|---|---|---|
Javascript Testing Best Practices | 21,983 | 2 months ago | 61 | mit | JavaScript | |||||
📗🌐 🚢 Comprehensive and exhaustive JavaScript & Node.js testing best practices (July 2023) | ||||||||||
Enzyme | 19,996 | 51,435 | 29,550 | 2 months ago | 54 | December 20, 2019 | 288 | mit | JavaScript | |
JavaScript Testing utilities for React | ||||||||||
Js Stack From Scratch | 19,396 | 2 | a year ago | 1 | January 19, 2017 | 48 | mit | JavaScript | ||
🛠️⚡ Step-by-step tutorial to build a modern JavaScript stack. | ||||||||||
Front End Guide | 14,903 | 2 | 4 months ago | 1 | June 22, 2017 | 25 | mit | JavaScript | ||
📚 Study guide and introduction to the modern front end stack. | ||||||||||
Tsdx | 10,906 | 435 | 12,690 | 4 months ago | 56 | October 13, 2020 | 237 | mit | JavaScript | |
Zero-config CLI for TypeScript package development | ||||||||||
React App Rewired | 9,404 | 2,532 | 1,284 | 10 months ago | 75 | February 15, 2022 | 13 | mit | JavaScript | |
Override create-react-app webpack configs without ejecting | ||||||||||
Majestic | 7,130 | 2 years ago | 42 | mit | TypeScript | |||||
⚡ Zero config GUI for Jest | ||||||||||
Jest Cheat Sheet | 5,045 | 3 months ago | cc0-1.0 | |||||||
Jest cheat sheet | ||||||||||
Pepperoni App Kit | 4,645 | 2 | 3 months ago | 4 | March 24, 2017 | 71 | mit | JavaScript | ||
Pepperoni - React Native App Starter Kit for Android and iOS | ||||||||||
Create React App Typescript | 3,769 | 3 | 4 years ago | 4 | January 25, 2018 | 119 | JavaScript | |||
DEPRECATED: Create React apps using typescript with no build configuration. |
I recommend Mrm and jest-codemods for single-command Jest installation and easy migration from other frameworks.
describe('makePoniesPink', () => {
beforeAll(() => {
/* Runs before all tests */
})
afterAll(() => {
/* Runs after all tests */
})
beforeEach(() => {
/* Runs before each test */
})
afterEach(() => {
/* Runs after each test */
})
test('make each pony pink', () => {
const actual = fn(['Alice', 'Bob', 'Eve'])
expect(actual).toEqual(['Pink Alice', 'Pink Bob', 'Pink Eve'])
})
})
expect(42).toBe(42) // Strict equality (===)
expect(42).not.toBe(3) // Strict equality (!==)
expect([1, 2]).toEqual([1, 2]) // Deep equality
expect({ a: undefined, b: 2 }).toEqual({ b: 2 }) // Deep equality
expect({ a: undefined, b: 2 }).not.toStrictEqual({ b: 2 }) // Strict equality (Jest 23+)
// Matches anything that an if statement treats as true (true, 1, 'hello', {}, [], 5.3)
expect('foo').toBeTruthy()
// Matches anything that an if statement treats as false (false, 0, '', null, undefined, NaN)
expect('').toBeFalsy()
// Matches only null
expect(null).toBeNull()
// Matches only undefined
expect(undefined).toBeUndefined()
// The opposite of toBeUndefined
expect(7).toBeDefined()
// Matches true or false
expect(true).toEqual(expect.any(Boolean))
expect(2).toBeGreaterThan(1)
expect(1).toBeGreaterThanOrEqual(1)
expect(1).toBeLessThan(2)
expect(1).toBeLessThanOrEqual(1)
expect(0.2 + 0.1).toBeCloseTo(0.3, 5)
expect(NaN).toEqual(expect.any(Number))
expect('long string').toMatch('str')
expect('string').toEqual(expect.any(String))
expect('coffee').toMatch(/ff/)
expect('pizza').not.toMatch('coffee')
expect(['pizza', 'coffee']).toEqual([expect.stringContaining('zz'), expect.stringMatching(/ff/)])
expect([]).toEqual(expect.any(Array))
expect(['Alice', 'Bob', 'Eve']).toHaveLength(3)
expect(['Alice', 'Bob', 'Eve']).toContain('Alice')
expect([{ a: 1 }, { a: 2 }]).toContainEqual({ a: 1 })
expect(['Alice', 'Bob', 'Eve']).toEqual(expect.arrayContaining(['Alice', 'Bob']))
expect({ a: 1 }).toHaveProperty('a')
expect({ a: 1 }).toHaveProperty('a', 1)
expect({ a: { b: 1 } }).toHaveProperty('a.b')
expect({ a: 1, b: 2 }).toMatchObject({ a: 1 })
expect({ a: 1, b: 2 }).toMatchObject({
a: expect.any(Number),
b: expect.any(Number),
})
expect([{ a: 1 }, { b: 2 }]).toEqual([
expect.objectContaining({ a: expect.any(Number) }),
expect.anything(),
])
// const fn = () => { throw new Error('Out of cheese!') }
expect(fn).toThrow()
expect(fn).toThrow('Out of cheese')
expect(fn).toThrowErrorMatchingSnapshot()
toThrowError
toThrow
expect(node).toMatchSnapshot()
// Jest 23+
expect(user).toMatchSnapshot({
date: expect.any(Date),
})
expect(user).toMatchInlineSnapshot()
// const fn = jest.fn()
// const fn = jest.fn().mockName('Unicorn') -- named mock, Jest 22+
expect(fn).toBeCalled() // Function was called
expect(fn).not.toBeCalled() // Function was *not* called
expect(fn).toHaveBeenCalledTimes(1) // Function was called only once
expect(fn).toBeCalledWith(arg1, arg2) // Any of calls was with these arguments
expect(fn).toHaveBeenLastCalledWith(arg1, arg2) // Last call was with these arguments
expect(fn).toHaveBeenNthCalledWith(callNumber, args) // Nth call was with these arguments (Jest 23+)
expect(fn).toHaveReturnedTimes(2) // Function was returned without throwing an error (Jest 23+)
expect(fn).toHaveReturnedWith(value) // Function returned a value (Jest 23+)
expect(fn).toHaveLastReturnedWith(value) // Last function call returned a value (Jest 23+)
expect(fn).toHaveNthReturnedWith(value) // Nth function call returned a value (Jest 23+)
expect(fn.mock.calls).toEqual([
['first', 'call', 'args'],
['second', 'call', 'args'],
]) // Multiple calls
expect(fn.mock.calls[0][0]).toBe(2) // fn.mock.calls[0][0] the first argument of the first call
toBeCalled
toHaveBeenCalled
toBeCalledWith
toHaveBeenCalledWith
lastCalledWith
toHaveBeenLastCalledWith
nthCalledWith
toHaveBeenNthCalledWith
toReturnTimes
toHaveReturnedTimes
toReturnWith
toHaveReturnedWith
lastReturnedWith
toHaveLastReturnedWith
nthReturnedWith
toHaveNthReturnedWith
expect(new A()).toBeInstanceOf(A)
expect(() => {}).toEqual(expect.any(Function))
expect('pizza').toEqual(expect.anything())
test('resolve to lemon', () => {
expect.assertions(1)
// Make sure to add a return statement
return expect(Promise.resolve('lemon')).resolves.toBe('lemon')
return expect(Promise.reject('octopus')).rejects.toBeDefined()
return expect(Promise.reject(Error('pizza'))).rejects.toThrow()
})
Or with async/await:
test('resolve to lemon', async () => {
expect.assertions(2)
await expect(Promise.resolve('lemon')).resolves.toBe('lemon')
await expect(Promise.resolve('lemon')).resolves.not.toBe('octopus')
})
See more examples in Jest docs.
Its a good practice to specify a number of expected assertions in async tests, so the test will fail if your assertions werent called at all.
test('async test', () => {
expect.assertions(3) // Exactly three assertions are called during a test
// OR
expect.hasAssertions() // At least one assertion is called during a test
// Your async tests
})
Note that you can also do this per file, outside any describe
and test
:
beforeEach(expect.hasAssertions)
This will verify the presense of at least one assertion per test case. It also plays nice with more specific expect.assertions(3)
declarations.
In addition, you can enforce it globally, across all test files (instead of having to repeat per file) by adding the exact same line into one of the scripts referenced by the setupFilesAfterEnv
configuration option. (For example, setupTests.ts
and that is referenced via a setupFilesAfterEnv: ['<rootDir>/setupTests.ts']
entry in jest.config.ts
.)
test('async test', async () => {
expect.assertions(1)
const result = await runAsyncOperation()
expect(result).toBe(true)
})
Return a Promise from your test:
test('async test', () => {
expect.assertions(1)
return runAsyncOperation().then((result) => {
expect(result).toBe(true)
})
})
Wrap your assertions in try/catch block, otherwise Jest will ignore failures:
test('async test', (done) => {
expect.assertions(1)
runAsyncOperation()
setTimeout(() => {
try {
const result = getAsyncOperationResult()
expect(result).toBe(true)
done()
} catch (err) {
done.fail(err)
}
})
})
test('call the callback', () => {
const callback = jest.fn()
fn(callback)
expect(callback).toBeCalled()
expect(callback.mock.calls[0][1].baz).toBe('pizza') // Second argument of the first call
// Match the first and the last arguments but ignore the second argument
expect(callback).toHaveBeenLastCalledWith('meal', expect.anything(), 'margarita')
})
You can also use snapshots:
test('call the callback', () => {
const callback = jest.fn().mockName('Unicorn') // mockName is available in Jest 22+
fn(callback)
expect(callback).toMatchSnapshot()
// ->
// [MockFunction Unicorn] {
// "calls": Array [
// ...
})
And pass an implementation to jest.fn
function:
const callback = jest.fn(() => true)
Your mocks can return values:
const callback = jest.fn().mockReturnValue(true)
const callbackOnce = jest.fn().mockReturnValueOnce(true)
Or resolve values:
const promise = jest.fn().mockResolvedValue(true)
const promiseOnce = jest.fn().mockResolvedValueOnce(true)
They can even reject values:
const failedPromise = jest.fn().mockRejectedValue('Error')
const failedPromiseOnce = jest.fn().mockRejectedValueOnce('Error')
You can even combine these:
const callback = jest.fn().mockReturnValueOnce(false).mockReturnValue(true)
// ->
// call 1: false
// call 2+: true
jest.mock
methodjest.mock('lodash/memoize', () => (a) => a) // The original lodash/memoize should exist
jest.mock('lodash/memoize', () => (a) => a, { virtual: true }) // The original lodash/memoize isnt required
Note: When using
babel-jest
, calls tojest.mock
will automatically be hoisted to the top of the code block. Usejest.doMock
if you want to explicitly avoid this behavior.
Create a file like __mocks__/lodash/memoize.js
:
module.exports = (a) => a
Add to your test:
jest.mock('lodash/memoize')
Note: When using
babel-jest
, calls tojest.mock
will automatically be hoisted to the top of the code block. Usejest.doMock
if you want to explicitly avoid this behavior.
const spy = jest.spyOn(console, 'log').mockImplementation(() => {})
expect(console.log.mock.calls).toEqual([['dope'], ['nope']])
spy.mockRestore()
const spy = jest.spyOn(ajax, 'request').mockImplementation(() => Promise.resolve({ success: true }))
expect(spy).toHaveBeenCalled()
spy.mockRestore()
const location = {}
const getTitle = jest.spyOn(location, 'title', 'get').mockImplementation(() => 'pizza')
const setTitle = jest.spyOn(location, 'title', 'set').mockImplementation(() => {})
const getTitle = jest.fn(() => 'pizza')
const setTitle = jest.fn()
const location = {}
Object.defineProperty(location, 'title', {
get: getTitle,
set: setTitle,
})
For one mock:
fn.mockClear() // Clears mock usage date (fn.mock.calls, fn.mock.instances)
fn.mockReset() // Clears and removes any mocked return values or implementations
fn.mockRestore() // Resets and restores the initial implementation
Note:
mockRestore
works only with mocks created byjest.spyOn
.
For all mocks:
jest.clearAllMocks()
jest.resetAllMocks()
jest.restoreAllMocks()
jest.mock('fs')
const fs = require('fs') // Mocked module
const fs = require.requireActual('fs') // Original module
Write synchronous test for code that uses native timer functions (setTimeout
, setInterval
, clearTimeout
, clearInterval
).
// Enable fake timers
jest.useFakeTimers()
test('kill the time', () => {
const callback = jest.fn()
// Run some code that uses setTimeout or setInterval
const actual = someFunctionThatUseTimers(callback)
// Fast-forward until all timers have been executed
jest.runAllTimers()
// Check the results synchronously
expect(callback).toHaveBeenCalledTimes(1)
})
Or adjust timers by time with advanceTimersByTime():
// Enable fake timers
jest.useFakeTimers()
test('kill the time', () => {
const callback = jest.fn()
// Run some code that uses setTimeout or setInterval
const actual = someFunctionThatUseTimers(callback)
// Fast-forward for 250 ms
jest.advanceTimersByTime(250)
// Check the results synchronously
expect(callback).toHaveBeenCalledTimes(1)
})
Use jest.runOnlyPendingTimers() for special cases.
Note: you should call jest.useFakeTimers()
in your test case to use other fake timer methods.
Run the same test with different data:
test.each([
[1, 1, 2],
[1, 2, 3],
[2, 1, 3],
])('.add(%s, %s)', (a, b, expected) => {
expect(a + b).toBe(expected)
})
Or the same using template literals:
test.each`
a | b | expected
${1} | ${1} | ${2}
${1} | ${2} | ${3}
${2} | ${1} | ${3}
`('returns $expected when $a is added $b', ({ a, b, expected }) => {
expect(a + b).toBe(expected)
})
Or on describe
level:
describe.each([['mobile'], ['tablet'], ['desktop']])('checkout flow on %s', (viewport) => {
test('displays success page', () => {
//
})
})
describe.each() docs, test.each() docs,
Dont run these tests:
describe.skip('makePoniesPink'...
tests.skip('make each pony pink'...
Run only these tests:
describe.only('makePoniesPink'...
tests.only('make each pony pink'...
Node.js and Jest will cache modules you require
. To test modules with side effects youll need to reset the module registry between tests:
const modulePath = '../module-to-test'
afterEach(() => {
jest.resetModules()
})
test('first test', () => {
// Prepare conditions for the first test
const result = require(modulePath)
expect(result).toMatchSnapshot()
})
test('second text', () => {
// Prepare conditions for the second test
const fn = () => require(modulePath)
expect(fn).toThrow()
})
Add babel-jest or ts-jest. Check their docs for installation instructions.
Improvements are welcome! Open an issue or send a pull request.
This software has been developed with lots of coffee, buy me one more cup to keep it going.
Artem Sapegin, a frontend engineer at Omio and the creator of React Styleguidist. I also write about frontend at my blog.
CC0 1.0 Universal license, see the included License.md file.