Skip to content

Instantly share code, notes, and snippets.

@bvarberg
Last active March 11, 2022 22:10
Show Gist options
  • Save bvarberg/c3a3ee4af0aa2f0cebaf0b15a5987f91 to your computer and use it in GitHub Desktop.
Save bvarberg/c3a3ee4af0aa2f0cebaf0b15a5987f91 to your computer and use it in GitHub Desktop.
Mocking patterns for Jest, and how to deal with dependencies that import ES Modules.
import { Dimensions } from "./Dimensions";
import { Visualizer } from "./Visualizer";
import { useBoard } from "./useBoard";
export function BoardViewer() {
const { board, changeDimension } = useBoard();
return (
<>
<Visualizer board={board} />
<Dimensions board={board} changeDimension={changeDimension} />
</>
);
}
import { render } from "@testing-library/react";
import td from "testdouble";
import { factories } from "../../testing/factories";
describe("BoardViewer", () => {
beforeEach(() => {
/**
* Provide a factory to prevent Jest from trying to automock these,
* which will throw some errors if the dependency being mocked imports
* anything in the ES Module format.
*/
jest.doMock("./useBoard", () => ({
useBoard: td.function("useBoard"),
}));
jest.doMock("./Visualizer", () => ({
Visualizer: td.component("Visualizer"),
}));
jest.doMock("./Dimensions", () => ({
Dimensions: td.component("Dimensions"),
}));
});
test("wires up a board, a visualizer, and a means for manipulating the board", async () => {
/**
* One difference is that we get references to the test doubles via dynamic import within
* the test block.
*/
const { useBoard } = await import("./useBoard");
const { Visualizer } = await import("./Visualizer");
const { Dimensions } = await import("./Dimensions");
const { BoardViewer } = await import("./BoardViewer");
const board = factories.board.build();
const changeDimension = td.function<any>("changeDimension");
td.when(useBoard()).thenReturn({
board,
changeDimension,
});
render(<BoardViewer />);
td.verify(Visualizer({ board }), { ignoreExtraArgs: true });
td.verify(Dimensions({ board, changeDimension }), {
ignoreExtraArgs: true,
});
});
});
import { render } from "@testing-library/react";
import td from "testdouble";
import { factories } from "../../testing/factories";
import { BoardViewer } from "./BoardViewer";
import { Dimensions } from "./Dimensions";
import { Visualizer } from "./Visualizer";
import { useBoard } from "./useBoard";
/**
* Let Jest automock these, and setup mock implementations in test blocks.
*/
jest.mock("./useBoard");
jest.mock("./Dimensions");
jest.mock("./Visualizer");
describe("BoardViewer", () => {
test("wires up a board, a visualizer, and a means for manipulating the board", async () => {
jest
.mocked(useBoard)
.mockImplementation(td.function<typeof useBoard>());
jest
.mocked(Dimensions)
.mockImplementation(td.component<typeof Dimensions>());
jest
.mocked(Visualizer)
.mockImplementation(td.component<typeof Visualizer>());
const board = factories.board.build();
const changeDimension = td.function<any>("changeDimension");
td.when(useBoard()).thenReturn({
board,
changeDimension,
});
render(<BoardViewer />);
td.verify(Visualizer({ board }), { ignoreExtraArgs: true });
td.verify(Dimensions({ board, changeDimension }), {
ignoreExtraArgs: true,
});
});
});
import { render } from "@testing-library/react";
import mock from "testdouble";
import { factories } from "../../testing/factories";
import { BoardViewer } from "./BoardViewer";
import { Dimensions } from "./Dimensions";
import { Visualizer } from "./Visualizer";
import { useBoard } from "./useBoard";
const td = mock;
/**
* Must provide a factory to prevent Jest from trying to automock them,
* which will throw some errors if the dependency being mocked imports
* anything in the ES Module format.
*/
jest.mock("./useBoard", () => ({
useBoard: mock.function(),
}));
jest.mock("./Dimensions", () => ({
Dimensions: mock.component(),
}));
jest.mock("./Visualizer", () => ({
Visualizer: mock.component(),
}));
describe("BoardViewer", () => {
test("wires up a board, a visualizer, and a means for manipulating the board", async () => {
const board = factories.board.build();
const changeDimension = td.function<any>("changeDimension");
td.when(useBoard()).thenReturn({
board,
changeDimension,
});
render(<BoardViewer />);
td.verify(Visualizer({ board }), { ignoreExtraArgs: true });
td.verify(Dimensions({ board, changeDimension }), {
ignoreExtraArgs: true,
});
});
});
import td from "testdouble";
import { setup } from "./packages/testdouble-component";
// Augment testdouble with td.component
setup(td);
beforeAll(() => {
// Disable warnings about stubbing + verifying on the same testdouble
// (workaround for the default stubbing setup via `td.component`)
td.config({
ignoreWarnings: true,
});
});
import { FunctionComponent } from "react";
export function setup(testdouble: any) {
const td = testdouble;
function component<FC extends FunctionComponent>(
displayName = "TestDoubleFunctionComponent"
) {
const ComponentDouble = td.function(displayName) as FC;
ComponentDouble.displayName = displayName;
// Hacky way to default to returning a fragment, instead of undefined
// see: https://github.com/testdouble/testdouble.js/issues/390
const anyProps = td.matchers.anything();
td.when(ComponentDouble(anyProps), { ignoreExtraArgs: true }).thenReturn(
<>{displayName}</>
);
return ComponentDouble;
}
td.component = component;
}
declare module "testdouble" {
/**
* Create a fake function component.
*
* @param displayName Name of function component for better messages.
*/
export function component<FC extends Function>(displayName?: string): FC;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment