Current problems:
The fundamental problem is not with the tool used (Enzyme in our case), it's with the
testing methodology.
Good test principles:
What in our existing codebase makes these principles difficult to follow?
* more arguments against shallow rendering: https://kentcdodds.com/blog/why-i-never-use-shallow-rendering
Examples of implementation detail testing:
asserting on components internals (e.g. `wrapper.instance` in Enzyme)
Solution:
treat components as 'black boxes' - instead of testing state, test the behavior visible on the outside (which matters for the end user).
import { mount } from "enzyme";
it("should render correct", () => {
expect(
mount(<FAQItem question="Question?" answer="Answer" />)
).toMatchSnapshot();
});
// lots of false negatives, test breaks after any change to the implementationimport { render } from "@testing-library/react";
import "@testing-library/jest-dom/extend-expect";
it("renders critical UI elements", () => {
const { getByLabelText } = render(
<FAQItem question="Question?" answer="Answer" />
);
const button = getByLabelText(/expand content/i);
const content = getByLabelText(/expanded content/i);
expect(button).toBeInTheDocument();
expect(content).toBeInTheDocument();
});
// asserts on existence of critical ui elements - test doesn't break after refactoringimport { shallow } from 'enzyme';
it("should call toggleAnswer correctly", () => {
const instance = shallow(
<FAQItem question="Question?" answer="Answer" />
).instance();
instance.toggleAnswer();
expect(instance.state.isOpen).toBe(true);
instance.toggleAnswer();
expect(instance.state.isOpen).toBe(false);
});
// uses shallow rendering
// asserts on implementation detailimport { fireEvent, render } from '@testing-library/react';
import "@testing-library/jest-dom/extend-expect";
it("expands when clicked", () => {
const { getByLabelText } = render(
<FAQItem question="Question?" answer="Answer" />
);
const button = getByLabelText(/expand content/i);
const content = getByLabelText(/expanded content/i);
expect(button).toHaveAttribute("aria-expanded", "false");
expect(content).toHaveAttribute("aria-hidden", "true");
fireEvent.click(button);
expect(button).toHaveAttribute("aria-expanded", "true");
expect(content).toHaveAttribute("aria-hidden", "false");
});
// - fully renders the component
// - asserts on behavior that matters to the end userimport styled from 'styled-components';
import { toMatchDiffSnapshot } from 'snapshot-diff';
expect.extend({ toMatchDiffSnapshot });
const MyInput = styled.input`
padding: ${p => p.iconPosition === 'left' ? '12px 12px 12px 45px' : '12px 45px 12px 12px'};
`
it('iconPosition prop change affects render', () => {
expect(
render(<MyInput iconPosition="left" />)
).toMatchDiffSnapshot(
render(<MyInput iconPosition="right" />)
);
});BONUS SLIDE: better use case for snapshots
How do we enforce consistent good practices on the project?
React testing library