https://github.com/lburdock/js-test

Overview

Test syntax

import foo from "./foo";

describe("foo", () => {
  test("works", () => {
    expect(foo()).toBe("bar");
  });
});

Test setup

beforeAll(() => {
  // Setup
});

beforeEach(() => {
  // Setup
});

afterEach(() => {
  // Breakdown
});

afterAll(() => {
  // Breakdown
});

Duplicating tests

test.each([
  [1, 2, 3],
  [4, 5, 9],
])("%i + %i === %i", (a, b, expected) => {
  expect(a + b).toBe(expected);
});

Duplicating test suites

describe.each([
  { a: 1, b: 2, expected: 3 },
  { a: 4, b: 5, expected: 9 },
])("$a + $b", ({ a, b, expected }) => {
  test(`to not be greater than ${expected}`, () => {
    expect(a + b).not.toBeGreaterThan(expected);
  });

  test(`to not be less than ${expected}`, () => {
    expect(a + b).not.toBeLessThan(expected);
  });
});

Vitest API

Matchers

expect(1).toBeTruthy();
expect(0).toBeFalsy();
expect(NaN).toBeNaN();
expect(null).toBeNull();
expect(undefined).toBeUndefined();
expect("a").toBeDefined();

expect(3).toBe(3);
expect(0.2 + 0.1).toBeCloseTo(0.3, 5);
expect(4).toBeGreaterThan(3);
expect(3).toBeGreaterThanOrEqual(3);
expect(2).toBeLessThan(3);
expect(3).toBeLessThanOrEqual(3);

expect("abc").toBe("abc");
expect("abc").toContain("b");
expect("abc").toMatch(/B/i);
expect("abc").toHaveLength(3);

const arr = ["a", { b: 1 }];
expect(arr).toStrictEqual(["a", { b: 1 }]);
expect(arr).toContain("a");
expect(arr).toContainEqual({ b: 1 });
expect(arr).toHaveLength(2);

const obj = { a: 1, b: 2 };
expect(obj).toStrictEqual({ a: 1, b: 2 });
expect(obj).toMatchObject({ a: 1 });
expect(obj).toHaveProperty("b", 2);

const kitchenSink = {
  id: "abc",
  total: 10,
  sum: 0.1 + 0.2,
  name: "Test 1",
  zip: "12345",
  options: ["Fuji", "Gala"],
  count: { Fuji: 1, Gala: 2 },
};
expect(kitchenSink).toStrictEqual({
  id: expect.anything(),
  total: expect.any(Number),
  sum: expect.closeTo(0.3, 5),
  name: expect.stringContaining("Test"),
  zip: expect.stringMatching(/\\d{5}$/i),
  options: expect.arrayContaining(["Gala"]),
  count: expect.objectContaining({ Fuji: 1 }),
});

expect(() => {
  errFunc();
}).toThrow(/nope/i);

Vitest expect

expect(el).toBeChecked();
expect(el).toBeDisabled();
expect(el).toBeEmptyDOMElement();
expect(el).toBeEnabled();
expect(el).toBeInTheDocument();
expect(el).toBeInvalid();
expect(el).toBePartiallyChecked();
expect(el).toBeRequired();
expect(el).toBeValid();
expect(el).toBeVisible();

expect(el).toContainElement(childEl);

expect(el).toHaveAccessibleDescription("Home");
expect(el).toHaveAccessibleErrorMessage("Error");
expect(el).toHaveAccessibleName("Alt text");
expect(el).toHaveAttribute("type", "submit");
expect(el).toHaveClass("cls1 cls2");
expect(el).toHaveDisplayValue("John");
expect(el).toHaveFocus();
expect(el).toHaveFormValues({ name: "John" });
expect(el).toHaveRole("button");
expect(el).toHaveStyle({ color: "red" });
expect(el).toHaveTextContent("Content");
expect(el).toHaveValue("John");

jest-dom

Vitest mocks

Implementation

const mock = vi.fn();
const mock = vi.fn(n => n * n);

const mock = vi
  .fn(n => n * n)
  .mockImplementationOnce(n => n * n * n);

const mock = vi
  .fn()
  .mockReturnValue("default")
  .mockReturnValueOnce("first");

const asyncMock = vi
  .fn()
  .mockResolvedValue("default")
  .mockResolvedValueOnce("first");

const asyncMock = vi
  .fn()
  .mockRejectedValue(new Error("default"))
  .mockRejectedValueOnce(new Error("first"));

mock.mockClear();
mock.mockReset();
mock.mockRestore();

vi.clearAllMocks();
vi.resetAllMocks();
vi.restoreAllMocks();

vi.stubEnv("NODE_ENV", "production");
vi.unstubAllEnvs();

vi.stubGlobal("IntersectionObserver", vi.fn());
vi.unstubAllGlobals();

Vitest mock

Mock-specific matchers

expect(mock).toHaveBeenCalled();
expect(mock).toHaveBeenCalledTimes(3);
expect(mock).toHaveBeenCalledWith("a");
expect(mock).toHaveBeenNthCalledWith(2, "b");
expect(mock).toHaveBeenLastCalledWith("c");

expect(mock.mock.calls).toEqual(["a"], ["b"]);
expect(mock.mock.calls[1][0]).toEqual("b");
expect(mock.mock.lastCall).toEqual(["c"]);

expect(mock).toHaveReturned();
expect(mock).toHaveReturnedTimes(3);
expect(mock).toHaveReturnedWith("x");
expect(mock).toHaveNthReturnedWith(2, "y");
expect(mock).toHaveLastReturnedWith("z");

expect(asyncMock).toHaveResolved();
expect(asyncMock).toHaveResolvedTimes(3);
expect(asyncMock).toHaveResolvedWith("x");
expect(asyncMock).toHaveLastResolvedWith(2, "y");
expect(asyncMock).toHaveNthResolvedWith("z");

Vitest expect

Mocking modules

Functions

// module-consumer.test.js
import { namedFnConsumer, defaultFnConsumer } from "./module-consumer";

const mockIsLiveNow = vi.hoisted(() => vi.fn());
const mockIsBeforeNow = vi.hoisted(() => vi.fn());

vi.mock("./module-to-mock", () => ({
  default: mockIsLiveNow,
  isBeforeNow: mockIsBeforeNow,
}));

afterEach(() => {
  mockIsLiveNow.mockReset();
  mockIsBeforeNow.mockReset();
});

test("namedFnConsumer", () => {
  mockIsBeforeNow.mockReturnValue(true);
  expect(namedFnConsumer("fake-date")).toBe("This date is in the past!");
});

test("defaultFnConsumer", () => {
  mockIsLiveNow.mockReturnValue(true);
  expect(defaultFnConsumer("fake-date")).toBe("This event is live!");
});
// module-consumer.js
import isLiveNow, { isBeforeNow } from "./module-to-mock";

export const defaultFnConsumer = date => {
  if (isLiveNow(date)) return "This event is live!";
  return "This event is not live.";
};

export const namedFnConsumer = date => {
  if (isBeforeNow(date)) return "This date is in the past!";
  return "This date is in the future!";
};

Vitest mock modules

Classes

// class-consumer.test.js
import { defaultClassConsumer, namedClassConsumer } from "./class-consumer";

const mockIsLiveNow = vi.hoisted(() => vi.fn());
const mockIsBeforeNow = vi.hoisted(() => vi.fn());

vi.mock("./class-to-mock", () => ({
  default: vi.fn(() => ({ isLiveNow: mockIsLiveNow })),
  NamedClassToMock: vi.fn(() => ({ isBeforeNow: mockIsBeforeNow })),
}));

afterEach(() => {
  mockIsLiveNow.mockReset();
  mockIsBeforeNow.mockReset();
});

test("defaultClassConsumer", () => {
  mockIsLiveNow.mockReturnValue(true);
  expect(defaultClassConsumer("fake-date")).toBe("This event is live!");
});

test("namedClassConsumer", () => {
  mockIsBeforeNow.mockReturnValue(true);
  expect(namedClassConsumer("fake-date")).toBe("This date is in the past!");
});
// class-consumer.js
import DefaultClassToMock, { NamedClassToMock } from "./class-to-mock";

export const defaultClassConsumer = date => {
  const myClass = new DefaultClassToMock();
  if (myClass.isLiveNow(date)) return "This event is live!";
  return "This event is not live.";
};

export const namedClassConsumer = date => {
  const myClass = new NamedClassToMock();
  if (myClass.isBeforeNow(date)) return "This date is in the past!";
  return "This date is in the future!";
};

React testing library

Test syntax

import {
  render,
  screen,
} from "@testing-library/react";

test("Button", () => {
  render(<button>Yep</button>);

  const btn = screen.getByRole("button");
  expect(btn).toBeInTheDocument();
});

React Testing Library example

Async test syntax

import {
  render,
  screen,
  waitFor,
} from "@testing-library/react";
import userEvent from "@testing-library/user-event";

test("Button async", async () => {
  render(<button>Yep</button>);

  const btn = await screen.findByRole("button");
  await expect(btn).toBeInTheDocument();

  // Alternative to the above
  await expect(
    screen.findByRole("button"),
  ).resolves.toBeInTheDocument();

  await expect(
    screen.findByText("Nope")
  ).rejects.toThrow();
});

Testing Library async methods

User event

import {
  render,
  screen,
} from "@testing-library/react";
import userEvent from "@testing-library/user-event";

test("Button onClick", async () => {
  const user = userEvent.setup();
  const onClick = vi.fn();
  render(<button onClick={onClick}>Yep</button>);

  await user.click(screen.getByRole("button"));
  await waitFor(() =>
    expect(onClick).toHaveBeenCalled(),
  );
});

User event functions

await user.clear(el);

await user.click(el);
await user.dblClick(el);
await user.tripleClick(el);

await user.hover(el);
await user.unhover(el);

await user.selectOptions(el, ["1", "C"]);
await user.selectOptions(el, screen.getByText("C"));
await user.deselectOptions(el, "B");

await user.tab();

await user.type(el, "Example");

await user.upload(el, file);

Testing Library user-event

Testing Library queries

variants No match 1 match 1+ match
getBy Error Element Error
getAllBy Error Element[] Element[]
findBy Promise<Error> Promise<Element> Promise<Error>
findAllBy Promise<Error> Promise<Element[]> Promise<Element[]>
queryBy null Element Error
queryAllBy [] Element[] Element[]

Testing Library types of queries

Query types

Aria role

getByRole
getAllByRole
queryByRole
queryAllByRole
findByRole
findAllByRole

Label or aria-label text

getByLabelText
getAllByLabelText
queryByLabelText
queryAllByLabelText
findByLabelText
findAllByLabelText

Input placeholder value

getByPlaceholderText
getAllByPlaceholderText
queryByPlaceholderText
queryAllByPlaceholderText
findByPlaceholderText
findAllByPlaceholderText

Element text content

getByText
getAllByText
queryByText
queryAllByText
findByText
findAllByText

Form element value

getByDisplayValue
getAllByDisplayValue
queryByDisplayValue
queryAllByDisplayValue
findByDisplayValue
findAllByDisplayValue

HTML element roles for the *ByRole queries

Options