18th Aug 2019

Snapshot Resilience

Naming mocked functions in Jest improves the quality and resilience of snapshot testing.

In the following React component we want to test the contract between the callbacks passed as props and their corresponding click handlers are correct. Clicking the "Sign in" button should call the onSignIn prop, and clicking "Sign up" should call onSignUp:


const WelcomeScreen = ({ onSignIn, onSignUp }) => (
  <>
    <button onClick={onSignIn}>Sign In</button>
    <button onClick={onSignUp}>Sign Up</button>
  </>
);

A simple snapshot captures this relationship between each button and its callback:


it("should match the snapshot", () => {
  const wrapper = shallow(
    <WelcomeScreen onSignIn={jest.fn()} onSignUp={jest.fn()} />
  );

  expect(wrapper.getElement()).toMatchSnapshot();
});

// Produces:

exports[`should match the snapshot 1`] = `
<React.Fragment>
  <button
    onClick={[MockFunction]}
  >
    Sign In
  </button>
  <button
    onClick={[MockFunction]}
  >
    Sign Up
  </button>
</React.Fragment>
`;

If we introduce a bug, and intensionally break the contract between the "Sign In" button and its callback the unit test still passes:


const WelcomeScreen = ({ onSignIn, onSignUp }) => (
  <>
    <button onClick={onSignUp}>Sign In</button> {/* 🔴 Wrongly calls onSignUp */}
    <button onClick={onSignUp}>Sign Up</button>
  </>
);

// Produces:

 PASS  src/WelcomeScreen.spec.js
  ✅ should match the snapshot (10ms)

The assertion passes because clicking "Sign In" still calls a function.

We know the wrong function will be called however our test doesn't.

This is where naming mock functions can help. We can make the snapshot aware of what function will be called using mockFn.mockName(value):


it("should match the snapshot", () => {
  const wrapper = shallow(
    <WelcomeScreen
      onSignIn={jest.fn().mockName("onSignInMock")}
      onSignUp={jest.fn().mockName("onSignUpMock")}
    />
  );

  expect(wrapper.getElement()).toMatchSnapshot();
});


// Produces:

exports[`should match the snapshot 1`] = `
<React.Fragment>
  <button
    onClick={[MockFunction onSignInMock]}
  >
    Sign In
  </button>
  <button
    onClick={[MockFunction onSignUpMock]}
  >
    Sign Up
  </button>
</React.Fragment>
`;

Now when the same bug is introduced the unit test fails:


const WelcomeScreen = ({ onSignIn, onSignUp }) => (
  <>
    <button onClick={onSignUp}>Sign In</button> {/* 🔴 Wrongly calls onSignUp */}
    <button onClick={onSignUp}>Sign Up</button>
  </>
);

// Produces:

 FAIL  src/WelcomeScreen.spec.js
  ❌ should match the snapshot (13ms)

  ● should match the snapshot

    expect(received).toMatchSnapshot()

    Snapshot name: `should match the snapshot 1`

    - Snapshot
    + Received

    @@ -1,8 +1,8 @@
      <React.Fragment>
        <button
    -     onClick={[MockFunction onSignInMock]}
    +     onClick={[MockFunction onSignUpMock]}
        >
          Sign In
        </button>
        <button
          onClick={[MockFunction onSignUpMock]}

Happy snapping!

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)