loading...

React Shallow Snapshot

thekashey profile image Anton Korzunov Updated on ・4 min read

As one my friend once said - In every joke there is a joke. The inner joke.

This is quite deep philosophical observation which means basically - you might derive one joke from another, making the first one even more funny. You may find endless reflections and nuances, making a little joke a real THING.

And here is the joke of mine:

Does not sounds funny? So let's find a real joke inside the joke, and just do that it said - let's create a shallow snapshot! :)

import React from 'react';
import Link from 'react-router';
import Section from './section';

// const Section = ({children}) => <section><SomeStuff/>{children}</section>

describe('Mark-testing', () => {
  it('should match snapshot', () => {
    expect(shallow(<Section><Link>click me</Link></Section>)).toMatchSnapshot());    
  });
});

What we just did? We tested a skeleton, and, long story shot, the result would be....

<section>
  <SomeStuff />
  <Link to=\\"#\\">
    click me
  </Link>
</section>

We tested nothing.

That was something expected. This is how shallow rendering works, and that's a feature, not a bug. A reason Why I Always Use Shallow Rendering.

But... you are right - that's absolutely useless 😒

So - shallow would not help is find the inner joke. Let's mount then!

describe('Mark-mount-testing', () => {
  it('should match snapshot', () => {
    expect(mount(<Section><Link>click me</Link></Section>)).toMatchSnapshot());    
  });
});

What would happen next? πŸ˜‰ Error: Uncaught [Error: Invariant failed: You should not use <Link> outside a <Router>]

So let's wrap everything a router and rerun. Now our snapshot is:

<StaticRouter>
  <Router history={{...}} staticContext={{...}}>
    <Section>
      <section>
        <SomeStuff>
          <div>
            Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry&#39;s standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.
          </div>
        </SomeStuff>
        <Link to=\\"#\\">
          <a onClick={[Function: onClick]} href=\\"/\\">
            click me
          </a>
        </Link>
      </section>
    </Section>
  </Router>
</StaticRouter>

You know - that's much better than before but at the same time a bit more than I expected.

In my personal experience, with StyledComponets, MoreComponents and MoreDeeperComponents snapshots could be HUGE and provide nothing more, but noise. Uter crap!

Could we find something in between?

React Testing Recipes has a few hints about making snapshots testing better:

It’s typically better to make more specific assertions than to use snapshots. These kinds of tests include implementation details so they break easily, and teams can get desensitized to snapshot breakages. Selectively mocking some child components can help reduce the size of snapshots and keep them readable for the code review.

By mocking they assumed jest.mock, but I've got another solution for you - mocking components, not modules.

Yes, piece a cake. Let's use a finite component idea, I've explained in depth here:

import React from 'react';
import Link from 'react-router';
import { createElement, remock } from 'react-remock';
import Section from './section';

describe('Mark-finite-mount-testing', () => {
  it('should match snapshot', () => {
    remock.push();

    // replace Link by another component
    remock.mock(Link, (_, props) => createElement('router-link', props));

    expect(shallow(<Section><Link>click me</Link></Section>)).toMatchSnapshot());   
    remock.pop(); 
  });
});

Here we just mocked a Link component, removing the dependency to Router and kept the rest unchanged. Updated snapshot is:

<Section>
  <section>
    <SomeStuff>
      <div>
        Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry&#39;s standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.
      </div>
    </SomeStuff>
    <router-link to="#">
      click me
    </router-link>
  </section>
</Section>

Clean and Sound. Just without some branches we cuted off, making your snapshot more shallow, but not as shallow, as using the real shallow 🀯

You might mock anything providing a variable, like I did, or name, or regexp. Cut, or replace (we kept children of a Link), as much as needed.

A good example is a FancyButton, which actually is a combination of 2 <ThemeProviders>, a few <StyledComponents>, and a few more HOCs.

This is a ONE Component for you

Oh CRAP

With remock you might make it a ONE component for your snapshot.

PS

Examples were provided are in the Jest syntax, while snapshots were generated using mocha + chai-jest-snapshot

const wrapper = mount(<Section><Link to="#">click me</Link></Section>);
expect(wrapper.debug()).to.matchSnapshot();
// ^ yes, wrapper.debug() ^ produces the best code, 
// while `.html()` or `.render` or hides some details(props) 
// you might wanna use, or generates a single line of code, 
// you will not able to review/merge.

So, you will not believe, but finite component and react-remock could make shallow snapshots a thing. If you use a mount... That was the joke :)

Mark authors best memes. I liked this one.

PS: And the other one - there is also the same joke about dependency mocking, and you know... for the things like stricter it's a fun!

Posted on by:

Discussion

markdown guide
 

So for FancyButton you would mock all those HOCs and then match the snapshot? And you'd end up with something simpler but still complex enough that it's worth maintaining the test?

Over the years, snapshot tests did find some bugs for me, but mostly because I know what they should look like and I could interpret the changes. I've had to decline PRs where the dev just updated snapshots without interpreting the changes. So I think it's best to use inline snapshots (so it's hard to miss and short-ish) and add some assertions about the most important parts of the snapshots.