Home

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.

Check out my book on clean code for frontend developers.

Table of contents

<!-- toc --> <!-- tocstop -->

Getting started

See the official docs

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

Matchers docs

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 the expect() 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 to await the expect() 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

Mock functions docs

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

Mock modules

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

  1. Create a file like __mocks__/lodash/memoize.js:

    module.exports = (a) => a
    
  2. 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]
The mockRestore() method works only with mocks created by vi.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

Fake times

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 call vi.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


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.