TypeOfNaN

Using Jest Mocks to Prevent Non-Deterministic or Otherwise Changing Components from Continously Resulting in Snapshot Diffs

Nick Scialli
August 30, 2020

failing tests

Snapshot testing is great. I work with React a lot and frequently use Storybook to do visual and snapshot testing of my components. It can help quickly identify any regressions and undesired behavior by pointing out when your components render differently than they did the last time you ran your test suite.

The Problem of Non-Deterministic or Otherwise-Changing Components

Snapshot diffs can sometimes be meaningless. For example, if you snapshot test a component that contains a random number generator, you’ll constantly get snapshot diffs when nothing has really changed. These diffs are essentially a nuisance—we don’t want to keep having to approve them over and over.

A Concrete Example: The Current Timestamp

Let’s say our app has a section that tells users the current time. My app will be an extreme example and will tell the time down to the second. Note that this is a functional React component with hooks. If you don’t understand the code, no worries, I’ll show you what gets rendered in the browser in a moment.

timer.jsx

import React, { useState, useEffect } from 'react';

const getCurrentTimestamp = () => new Date().toLocaleTimeString();

export const Timer = () => {
  const [time, setTime] = useState(getCurrentTimestamp());
  useEffect(() => {
    let refresh = setInterval(() => {
      setTime(getCurrentTimestamp());
    }, 1000);
    return () => {
      clearInterval(refresh);
    };
  }, []);

  return <div>The time is {time}</div>;
};

If I fire up my app and load it in the browser, I see a very simple dynamic clock.

simple clock in browser

Snapshot Testing our Clock

Here’s a really simply Storybook test for my Timer component.

import React from 'react';
import { Timer } from '../timer';

export default {
  title: 'Components/Timer',
  component: Timer,
};

export const Default = () => <Timer />;

However, whenever I run snapshot testing for this component, it fails!

failed test

The time changes every time I take a snapshot of the component, so of course the snapshot will fail: we get something different rendering every time.

Fixing the Issue: Mocking Out Our Dynamic Component

Fortunately, there’s a pretty simple fix for our issue! In this instance, our Storybook-related snapshots are driven by a config file, storybook.test.jsx. Prior to any modification, this config file looks like this:

import initStoryshots from '@storybook/addon-storyshots';
initStoryshots();

We can modify this file by mocking the import of Timer from the timer.jsx file! That way, any use of the component in our snapshot tests will use the mock we provide here rather than the actual component.

import React from 'react';
import initStoryshots from '@storybook/addon-storyshots';

jest.mock('../timer.tsx', () => ({
  Timer: () => <timer />,
}));

initStoryshots();

Here we’ve basically told jest that, instead of rendering our dynamic component for snapshot tests, just go ahead and render <timer />. That way, we won’t be bothered by dynamic snapshots but we’ll also be assured that the timer is rendering when it should.

We can now run our snapshot tests and will no longer be annoyed by these frequently-changing components!

Nick Scialli

Nick Scialli is a senior UI engineer at Microsoft.

© 2024 Nick Scialli