Awesome
<!-- To update TOC run: npx markdown-toc --maxdepth 3 -i Readme.md To reformat run: npx prettier --print-width 100 --single-quote --no-semi --prose-wrap never --write Readme.md --> <div align="center" markdown="1"> <img src="https://vitest.dev/logo.svg" width="200"> <h1>Vitest cheat sheet</h1> </div>This is a fork of the popular Jest cheat sheet.
Table of contents
<!-- toc -->- Getting started
- Test structure
- Matchers
- Async tests
- Mocks
- Data-driven tests
- Skipping tests
- Testing modules with side effects
- Resources
- Contributing
- Sponsoring
- Author and license
Getting started
Test structure
Basic test structure
[!NOTE]
In comparison to Jest, in Vitest you need to explicitly import all methods
import { expect, test } from 'vitest'
import { makePoniesPink } from './makePoniesPink'
test('make each pony pink', () => {
const actual = makePoniesPink(['Alice', 'Bob', 'Eve'])
expect(actual).toEqual(['Pink Alice', 'Pink Bob', 'Pink Eve'])
})
Test with grouping, setup and teardown code
import { expect, test, beforeAll, afterAll , beforeEach,afterEach} from 'vitest'
import { makePoniesPink } from './makePoniesPink'
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 = makePoniesPink(['Alice', 'Bob', 'Eve'])
expect(actual).toEqual(['Pink Alice', 'Pink Bob', 'Pink Eve'])
})
Matchers
Basic matchers
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+)
Truthiness
// 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))
Numbers
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))
Strings
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/)])
Arrays
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']))
Objects
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(),
])
Exceptions
// const fn = () => { throw new Error('Out of cheese!') }
expect(fn).toThrowError()
expect(fn).toThrowError('Out of cheese')
expect(fn).toThrowErrorMatchingSnapshot()
expect(fn).toThrowErrorMatchingInlineSnapshot()
// const fn = () => { console.error('Out of cheese!') }
expect(fn).not.toThrowError()
expect(fn).not.toThrowError('Out of cheese')
Snapshots
expect(node).toMatchSnapshot()
expect(user).toMatchSnapshot({
date: expect.any(Date),
})
expect(user).toMatchInlineSnapshot()
Misc
expect(new A()).toBeInstanceOf(A)
expect(() => {}).toEqual(expect.any(Function))
expect('pizza').toEqual(expect.anything())
Promise matchers
[!WARNING]
Don’t forget to return the result of theexpect()
method from each test case.
test('resolves with a lemon', () => {
return expect(Promise.resolve('lemon')).resolves.toBe('lemon')
})
test('rejects with an octopus', () => {
return expect(Promise.reject('octopus')).rejects.toBeDefined()
})
test('rejects with an error', () => {
return expect(Promise.reject(Error('pizza'))).rejects.toThrow()
})
Or with async/await:
[!WARNING]
Don’t forget toawait
theexpect()
method in each test case.
test('resolves with a lemon', async () => {
await expect(Promise.resolve('lemon')).resolves.toBe('lemon')
})
test('doesn’t resolve with an octopus', async () => {
await expect(Promise.resolve('lemon')).resolves.not.toBe('octopus')
})
Async tests
async/await
test('async test', async () => {
const result = await runAsyncOperation()
expect(result).toBe(true)
})
Promises
Return a Promise from your test:
test('async test', () => {
return runAsyncOperation().then((result) => {
expect(result).toBe(true)
})
})
Mocks
Mock functions
import { expect, vi } from 'vitest'
// const fn = vi.fn()
// const fn = vi.fn().mockName('Unicorn') -- named mock, Jest 22+
expect(fn).toHaveBeenCalled() // Function was called
expect(fn).not.toHaveBeenCalled() // Function was *not* called
expect(fn).toHaveBeenCalledTimes(1) // Function was called only once
expect(fn).toHaveBeenCalledWith(arg1, expect.anything(), arg3) // 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+)
// Multiple calls
expect(fn.mock.calls).toEqual([
['first', 'call', 'args'],
['second', 'call', 'args'],
])
// fn.mock.calls[0][0] — the first argument of the first call
expect(fn.mock.calls[0][0]).toBe(2)
You can also use snapshots:
test('call the callback', () => {
const callback = vi.fn().mockName('Unicorn') // mockName is available in Jest 22+
fn(callback)
expect(callback).toMatchSnapshot()
// ->
// [MockFunction Unicorn] {
// "calls": Array [
// ...
})
And pass an implementation to vi.fn
function:
const callback = vi.fn(() => true)
Returning, resolving and rejecting values
Your mocks can return values:
const callback = vi.fn().mockReturnValue(true)
const callbackOnce = vi.fn().mockReturnValueOnce(true)
Or resolve values:
const promise = vi.fn().mockResolvedValue(true)
const promiseOnce = vi.fn().mockResolvedValueOnce(true)
They can even reject values:
const failedPromise = vi.fn().mockRejectedValue('Error')
const failedPromiseOnce = vi.fn().mockRejectedValueOnce('Error')
You can even combine these:
const callback = vi.fn().mockReturnValueOnce(false).mockReturnValue(true)
// ->
// call 1: false
// call 2+: true
Mock modules using vi.mock()
method
import { vi } from 'vitest'
// The original lodash/memoize should exist
vi.mock('lodash/memoize', () => (a) => a)
// The original lodash/memoize isn’t required
vi.mock('lodash/memoize', () => (a) => a, { virtual: true })
Mock modules using a mock file
-
Create a file like
__mocks__/lodash/memoize.js
:module.exports = (a) => a
-
Add to your test:
vi.mock('lodash/memoize')
Mock object methods
import { vi } from 'vitest'
const spy = vi.spyOn(console, 'log').mockImplementation(() => {})
expect(console.log.mock.calls).toEqual([['dope'], ['nope']])
spy.mockRestore()
import { vi } from 'vitest'
const spy = vi.spyOn(ajax, 'request').mockImplementation(() => Promise.resolve({ success: true }))
expect(spy).toHaveBeenCalled()
spy.mockRestore()
Mock getters and setters
import { vi } from 'vitest'
const location = {}
const getTitle = vi.spyOn(location, 'title', 'get').mockImplementation(() => 'pizza')
const setTitle = vi.spyOn(location, 'title', 'set').mockImplementation(() => {})
Clearing and restoring mocks
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]
ThemockRestore()
method works only with mocks created byvi.spyOn()
.
For all mocks:
vi.clearAllMocks()
vi.resetAllMocks()
vi.restoreAllMocks()
Accessing the original module when using mocks
import { vi } from 'vitest'
vi.mock('./example.js', async () => {
const axios = await vi.importActual('./example.js')
return { ...axios, get: vi.fn() }
})
Timer mocks
Write synchronous test for code that uses native timer functions (setTimeout
, setInterval
, clearTimeout
, clearInterval
).
// Enable fake timers
vi.useFakeTimers()
test('kill the time', () => {
const callback = vi.fn()
// Run some code that uses setTimeout or setInterval
const actual = someFunctionThatUseTimers(callback)
// Fast-forward until all timers have been executed
vi.runAllTimers()
// Check the results synchronously
expect(callback).toHaveBeenCalledTimes(1)
})
Or adjust timers by time with vi.advanceTimersByTime():
// Enable fake timers
vi.useFakeTimers()
test('kill the time', () => {
const callback = vi.fn()
// Run some code that uses setTimeout or setInterval
const actual = someFunctionThatUseTimers(callback)
// Fast-forward for 250 ms
vi.advanceTimersByTime(250)
// Check the results synchronously
expect(callback).toHaveBeenCalledTimes(1)
})
Use vi.runOnlyPendingTimers() for special cases.
![NOTE]
You should callvi.useFakeTimers()
in your test case to use other fake timer methods.
Data-driven tests
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,
Skipping tests
Don’t 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'...
Testing modules with side effects
Node.js and Vitest cache modules you import
. To test modules with side effects you’ll need to reset the module registry between tests:
const modulePath = '../module-to-test'
afterEach(() => {
vi.resetModules()
})
test('first test', async () => {
// Prepare conditions for the first test
const result = await import(modulePath)
expect(result).toMatchSnapshot()
})
test('second text', async () => {
// Prepare conditions for the second test
const fn = () => await import(modulePath)
expect(fn).toThrowError()
})
Resources
- Vitest site
- Modern React testing, part 1: best practices by Artem Sapegin
- Modern React testing, part 2: Jest and Enzyme by Artem Sapegin
- Modern React testing, part 3: Jest and React Testing Library by Artem Sapegin
- Modern React testing, part 4: Cypress and Cypress Testing Library by Artem Sapegin
- Modern React testing, part 5: Playwright by Artem Sapegin
Contributing
Improvements are welcome! Open an issue, or send a pull request.
Sponsoring
This software has been developed with lots of coffee, buy me one more cup to keep it going.
<a href="https://www.buymeacoffee.com/sapegin" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/lato-orange.png" alt="Buy Me A Coffee" height="51" width="217" ></a>
Author and license
Artem Sapegin, a frontend engineer at Stage+ 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.