27th Nov 2019

Test Like an Artist

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:


/**
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:


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();
      });
    });
  });
});

Never Miss a Post!

If you have enjoyed this blog post I invite you to join my newsletter:

Emails are kept safe with MailChimp And I never ever send spammy emails (Or stay updated with RSS)