Skip to content Accessibility

Test Like an Artist

Planning unit/integration tests before writing assertions.

Published

Categories

Much like an artist sketches before painting, or an author note takes before writing, we can comment before asserting.

Before starting a single describe/it/expect block it can be useful to plan a tests flow of control using comments.

It looks something like this:

Programming language (abbreviated): js

/**
Given an Example component
when rendered
it should match the snapshot
and the hasPromotion prop is true
it should render the Promotion component
and the errorMessage prop is present
it should render the ErrorMessage component
when the continue button is clicked
it should call the onContinue prop
when the back button is clicked
it should call the onBack prop
*/

The comments are in plain spoken language, do not contain any code, and focus solely on the semantics.

At this stage we can zero in on:

  • deciding what needs to be tested
  • finding the best logical sequence of assertions
  • defining crisp and consistent descriptions
  • identifying what mock data is needed (if any)

I have also found that moving, rewording and deleting comments is less scary (and therefore more likely) than doing so with code, which encourages refinement.

Once happy with the semantics the blanks can be filled in. This is where to focus on test setup, mocks, assertions, and hooks:

Programming language (abbreviated): js

describe('Given an Example component', () => {
const QA = {
continueButton: "[data-test='continue-button']",
backButton: "[data-test='back-button']"
};

describe('when rendered', () => {
const onContinueMock = jest.fn().mockName('onContinueMock');
const onBackMock = jest.fn().mockName('onBackMock');
let wrapper;

beforeEach(() => {
wrapper = shallow(<ExampleComponent onContinue={onContinueMock} onBack={onBackMock} />);
});

afterEach(() => jest.resetAllMocks());

it('should match the snapshot', () => {
expect(wrapper.getElement()).toMatchSnapshot();
});

describe('and the hasPromotion prop is true', () => {
beforeEach(() => {
wrapper.setProps({
hasPromotion: true
});
});

it('should render the Promotion component', () => {
expect(wrapper.find(Promotion)).toExist();
expect(wrapper.getElement()).toMatchSnapshot();
});
});

describe('and the errorMessage prop is present', () => {
beforeEach(() => {
wrapper.setProps({
errorMessage: 'Given error message'
});
});

it('should render the ErrorMessage component', () => {
expect(wrapper.find(ErrorMessage)).toExist();
expect(wrapper.getElement()).toMatchSnapshot();
});
});

describe('when the continue button is clicked', () => {
beforeEach(() => {
wrapper.find(QA.continueButton).simulate('click');
});

it('should call the onContinue prop', () => {
expect(onContinueMock).toHaveBeenCalledWith();
});
});

describe('when the back button is clicked', () => {
beforeEach(() => {
wrapper.find(QA.backButton).simulate('click');
});

it('should call the onBack prop', () => {
expect(onBackMock).toHaveBeenCalledWith();
});
});
});
});

About the author

I'm Callum, a Front-end Engineer at Nutmeg. Previously I wrote code for KAYAK, American Express, and Dell. Out of hours I publish blog posts (like this one) and tweet cherry-picks.

Feel free to follow or message me at @_callumhart on Twitter.