Last active
March 11, 2022 22:10
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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} /> | |
</> | |
); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | |
}); | |
}); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | |
}); | |
}); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | |
}); | |
}); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | |
}); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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