@testing-library/react uses data-testid="your-id" as a custom prop, which bypasses brittleness when testing with other selectors, while making it clear which parts of the component are tested.as such, testing for whether isModal is true is not as good as testing for the existence of the modal element.
import React from "react";
import "jest-styled-components";
import renderer from "react-test-renderer";
import { Selection } from "./";
import { SelectionProps } from "./types";
import { render, fireEvent, act } from "@testing-library/react";
// import { mount, shallow } from "enzyme";
// ref: https://www.robertcooper.me/testing-stateful-react-function-components-with-react-testing-library
// ref: https://github.com/cedrickchee/react-typescript-jest-enzyme-testing/blob/master/src/checkboxWithLabel.test.tsx
// ref: https://github.com/OfficeDev/office-ui-fabric-react/blob/master/packages/office-ui-fabric-react/src/components/Checkbox/Checkbox.test.tsx
describe("components/Selection", () => {
const handleChangeMock = jest.fn();
let props: SelectionProps;
beforeEach(() => {
props = {
type: "checkbox",
onChange: handleChangeMock,
options: [
{ label: "one", checked: true },
{ label: "two", checked: false },
],
};
handleChangeMock.mockClear();
});
it("should match snapshot", () => {
const tree = renderer.create(<Selection {...props} type="checkbox" />).toJSON();
expect(tree).toMatchSnapshot();
});
it("should render checkbox with type correctly", () => {
const { getAllByRole } = render(<Selection {...props} type="checkbox" />);
const checkboxes = getAllByRole("checkbox");
expect(checkboxes.length).toBe(2);
checkboxes.forEach((el) => {
expect(el).toHaveProperty("type", "checkbox");
});
});
it("should render radio with type correctly", () => {
const { getAllByRole } = render(<Selection {...props} type="radio" />);
const radios = getAllByRole("radio");
expect(radios.length).toBe(2);
radios.forEach((el) => {
expect(el).toHaveProperty("type", "radio");
});
});
it("(checkbox) should default to false when not given checked prop", () => {
const options = [{ label: "one" }, { label: "two" }];
const { getAllByRole } = render(<Selection {...props} options={options} type="checkbox" />);
const checkboxes: HTMLInputElement = getAllByRole("checkbox");
expect(checkboxes[0].checked).toBe(false);
expect(checkboxes[1].checked).toBe(false);
});
it("(radio) should default to false when not given checked prop", () => {
const options = [{ label: "one" }, { label: "two" }];
const { getAllByRole } = render(<Selection {...props} options={options} type="radio" />);
const radios: HTMLInputElement = getAllByRole("radio");
expect(radios[0].checked).toBe(false);
expect(radios[1].checked).toBe(false);
});
it("should respect default checkbox prop", () => {
const options = [
{ label: "one", checked: true },
{ label: "two", checked: false },
];
const { getAllByRole } = render(<Selection {...props} options={options} type="checkbox" />);
const checkboxes: HTMLInputElement = getAllByRole("checkbox");
expect(checkboxes[0].checked).toBe(true);
expect(checkboxes[1].checked).toBe(false);
});
it("should respect default radio prop", () => {
const options = [
{ label: "one", checked: false },
{ label: "two", checked: true },
];
const { getAllByRole } = render(<Selection {...props} options={options} type="radio" />);
const radios: HTMLInputElement = getAllByRole("radio");
expect(radios[0].checked).toBe(false);
expect(radios[1].checked).toBe(true);
});
it("should trigger onChange when clicked", () => {
const { getAllByRole } = render(<Selection {...props} type="checkbox" />);
const checkboxes = getAllByRole("checkbox");
fireEvent.click(checkboxes[0]);
expect(handleChangeMock).toHaveBeenCalledTimes(1);
});
it("should return the right checkbox states after change", () => {
const options = [
{ label: "one", checked: true },
{ label: "two", checked: false },
];
const { getAllByRole } = render(<Selection {...props} options={options} type="checkbox" />);
const checkboxes = getAllByRole("checkbox");
fireEvent.click(checkboxes[0]);
expect(handleChangeMock).toHaveBeenCalledTimes(1);
expect(handleChangeMock).toHaveBeenCalledWith([
{ label: "one", checked: false },
{ label: "two", checked: false },
]);
fireEvent.click(checkboxes[1]);
expect(handleChangeMock).toHaveBeenCalledTimes(2);
expect(handleChangeMock).toHaveBeenCalledWith([
{ label: "one", checked: false },
{ label: "two", checked: true },
]);
});
it("should return the right radio states after change", () => {
const options = [
{ label: "one", checked: true },
{ label: "two", checked: false },
];
const { getAllByRole } = render(<Selection {...props} options={options} type="radio" />);
const radios = getAllByRole("radio");
fireEvent.click(radios[0]);
expect(handleChangeMock).toHaveBeenCalledTimes(0); // clicking on an already selected radio
fireEvent.click(radios[1]);
expect(handleChangeMock).toHaveBeenCalledTimes(1);
expect(handleChangeMock).toHaveBeenCalledWith([
{ label: "one", checked: false },
{ label: "two", checked: true },
]);
fireEvent.click(radios[0]);
expect(handleChangeMock).toHaveBeenCalledTimes(2);
expect(handleChangeMock).toHaveBeenCalledWith([
{ label: "one", checked: true },
{ label: "two", checked: false },
]);
});
});the example below shows the testing of urls(), that uses an external library "unsplash-js" to fetch data. during the test, however, you can mock the library using jest.mock() that would magically listen to an import
import { urls } from "../Unsplash";
// the mock of the "unsplash-js" library should
// have all the properties that make it possible
// to call its api and return results
jest.mock("unsplash-js", () => ({
__esModule: true, //https://remarkablemark.org/blog/2018/06/28/jest-mock-default-named-export/
default: jest.fn(() => ({
search: {
photos: jest.fn(() => ({
json: jest.fn(() =>
Promise.resolve({
results: Array.from(Array(10)).map((v, i) => ({
urls: { regular: `url-stub-${i}` },
})),
})
),
})),
},
})),
}));
describe("Unsplash API", () => {
test("it should return a different url each time urls() is called", async () => {
// Arrange
const searchTerm = "inspirational";
// Act
const getImg = urls(searchTerm); // thunk
const img1 = await getImg();
const img2 = await getImg();
// Assert
expect(img1).not.toEqual(img2);
});
});almost exactly like mocking a library (a library is an exported module/function after all). the caveats are that when you us jest.mock(), you will replace all the functions in the file with what you have in jest.mock(). if your tested function calls another function from the mocked file, it will look for it inside jest.mock().
to solve that, use jest.requireActual() inside your jest.mock() and spread all exports so that they are accessible to your test function. ref
jest.mock('./myModule.js', () => (
{
...jest.requireActual('./myModule.js'),
otherFn: () => {}
}
))
describe(...)a more complex example:
// don't import
// require after you mock
describe("fetchAdhocData", () => {
beforeEach(() => {
jest.resetModules();
});
it('should return an object with "bodyData" and "headerData" keys if fetch suceeds', async () => {
// arrange
// mock request-promise library (success response)
jest.doMock("request-promise", () => {
const mockSuccessResponse = {
statusCode: 200,
body: '{"stub":"stub"}',
};
console.log("MOCKING for fetchAdhocData");
return {
__esModule: true,
default: jest.fn().mockResolvedValue(mockSuccessResponse),
};
});
const { fetchData } = require("./utils.ts");
const params = {
uri: "stub",
headerSql: "stub",
bodySql: "stub",
};
// act
const response = await fetchData(params);
// assert
expect(response).toHaveProperty("bodyData");
expect(response).toHaveProperty("headerData");
});
it('should return an object with "err" key if fetch fails', async () => {
// arrange
// failure mock
jest.doMock("request-promise", () => ({
__esModule: true,
default: jest.fn(
() =>
new Promise((_, reject) => {
process.nextTick(() => reject({ stack: "stub" }));
})
),
}));
const { fetchData } = require("./utils.ts");
const params = {
uri: "stub",
headerSql: "stub",
bodySql: "stub",
};
// act
const errorResult = await fetchData(params);
// assert
expect(errorResult).toHaveProperty("err");
});
});not possible. only exported functions are visible to the outside, so if you need to test a function that uses another local function (function without export), you can't. you have to move it out (e.g. into a utils file) and mock that file.
jest.mock('../your/HOC', () => () =>
Component => props => <Component {...props} />
)
// breakdown
jest.mock('../your/HOC', () => /* a function */)
// the function is the mock HOC, where propsFromWrapper is the wrapper component
// () => Wrapped => propsFromWrapper => <Wrapped {...propsFromWrapper}>
() => Component => props => <Component {...props}/>const { container } = render(<Foo bar={true} />);
// update the props, re-render to the same container (this means the component will not be unmounted / remounted)
render(<Foo bar={false} />, { container });
// can still use the same container and all other utilities
// you could also make a little utility
const updateProps = (props) => render(<Foo {...props} />, { container });
updateProps({ bar: false });yarn test --coverage --collectCoverageFrom="src/app/components/Tools/**/*.js"developing with jest
TZ=UTC NODE_PATH=src NODE_ENV='development' jest --no-cache --watch --coverage yourFilename --collectCoverageFrom='["src/**/yourFilename/**"]'@testing-library/react