Jest-styled-components: Snapshot output keep changing on every save action (CRA, Enzyme)

Created on 27 Nov 2017  ·  9Comments  ·  Source: styled-components/jest-styled-components

Hey there,

I'm running into some issues and I can't tell what I'm doing wrong.

I'm using Create React App (using React 16.1) with Enzyme (3.2.0) and Styled Components 2.2.3.

This is the component I'm trying to test:

// @flow

import React, { Component } from 'react'
import { Switch } from 'react-router'
import { Route } from 'react-router-dom'
import styled from 'styled-components'
import routes from './routes'
import DebugMenu from './components/DebugMenu'

type Props = {
}

const RouteWithSubRoutes = (route) => {
  return (
    <Route path={route.path} render={props => {
      return <route.component {...props} routes={route.routes} />
    }} />
  )
}

class App extends Component<Props> {
  render () {
    return (
      <AppWrapper>
        {process.env.NODE_ENV === 'development' &&
          <DebugMenu routes={routes} />
        }
        <Switch>
          {routes.map((route, i) => (
            <RouteWithSubRoutes key={i} {...route} />
          ))}
        </Switch>
      </AppWrapper>
    )
  }
}

const AppWrapper = styled.div`
  position: absolute;
  width: 100%;
  height: 100%;
`

export default App

And this is my test:

import React from 'react'
import { shallow } from 'enzyme'
import App from './App'
import { MemoryRouter } from 'react-router-dom'
// import renderer from 'react-test-renderer'

describe('App Specs', () => {
  it('Should match with the snapshot for this component', () => {
    const wrapper = shallow(
      <MemoryRouter initialEntries={['/']}>
        <App />
      </MemoryRouter>
    )
    expect(wrapper).toMatchSnapshot()
  })
})

I've configured my test setup for Enzyme by setting a setupTest.js file:

import Enzyme, { shallow, render, mount } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import toJson from 'enzyme-to-json'
import 'jest-styled-components'

// React 16 Enzyme adapter
Enzyme.configure({ adapter: new Adapter() })
// Make Enzyme functions available in all test files without importing
global.shallow = shallow
global.render = render
global.mount = mount
global.toJson = toJson

// Fail tests on any warning
console.error = message => {
  throw new Error(message)
}

When I run yarn test in watch mode, every time I save, the snapshot for this test changes and thus fails:

 FAIL  src/App.spec.js
  ● App Specs › Should match with the snapshot for this component

    expect(value).toMatchSnapshot()

    Received value does not match stored snapshot 1.

    - Snapshot
    + Received

    @@ -29,11 +29,11 @@
             "canGo": [Function],
             "createHref": [Function],
             "entries": Array [
               Object {
                 "hash": "",
    -            "key": "0af0tp",
    +            "key": "u968bq",
                 "pathname": "/",
                 "search": "",
                 "state": undefined,
               },
             ],
    @@ -43,11 +43,11 @@
             "index": 0,
             "length": 1,
             "listen": [Function],
             "location": Object {
               "hash": "",
    -          "key": "0af0tp",
    +          "key": "u968bq",
               "pathname": "/",
               "search": "",
               "state": undefined,
             },
             "push": [Function],
    @@ -79,11 +79,11 @@
               "canGo": [Function],
               "createHref": [Function],
               "entries": Array [
                 Object {
                   "hash": "",
    -              "key": "0af0tp",
    +              "key": "u968bq",
                   "pathname": "/",
                   "search": "",
                   "state": undefined,
                 },
               ],
    @@ -93,11 +93,11 @@
               "index": 0,
               "length": 1,
               "listen": [Function],
               "location": Object {
                 "hash": "",
    -            "key": "0af0tp",
    +            "key": "u968bq",
                 "pathname": "/",
                 "search": "",
                 "state": undefined,
               },
               "push": [Function],

      at Object.it (src/App.spec.js:14:21)
          at Promise (<anonymous>)
      at Promise.resolve.then.el (node_modules/p-map/index.js:46:16)
          at <anonymous>
      at process._tickCallback (internal/process/next_tick.js:169:7)

What am I doing wrong?

Most helpful comment

You can also pass key length through keyLength props and keep its value as 0. Ex: keyLength={0}. This will make sure, key doesn't change on each test run.

<MemoryRouter initialEntries={['/']} keyLength={0}>
   <App />
</MemoryRouter>

All 9 comments

You'll actually want to configure your jest framework startup file not the test startup file with the jest-styled-components.

your jest config should have this added to it in the package.json file

"setupTestFrameworkScriptFile": "<rootDir>/jest-setup-framework.js"

right now mine is only used for jest-styled components so in that .js file I have

import 'jest-styled-components';

and that's it.

to be clear my package json has both setup files, like this:

jest: {
    ...
    "setupFiles": [
      "raf/polyfill",
      "<rootDir>/jest-setup.js"
    ],
    "setupTestFrameworkScriptFile": "<rootDir>/jest-setup-framework.js"
}

Hey @abharvey, when I do that I get the following after running yarn test:

Out of the box, Create React App only supports overriding these Jest options:

  • collectCoverageFrom
  • coverageReporters
  • coverageThreshold
  • snapshotSerializers.

These options in your package.json Jest configuration are not currently supported by Create React App:

  • setupTestFrameworkScriptFile

If you wish to override other Jest options, you need to eject from the default setup. You can do so by running npm run eject but remember that this is a one-way operation. You may also file an issue with Create React App to discuss supporting more options out of the box.

So is the only way to use jest-styled-components by ejecting from Create React App?

Yes, it looks like the best choice for you is to eject from CRA, it's a moment we all get to when the app becomes custom enough. The other choice you have of course, as painful as it is, would be to declare the import in each test file that uses a styled component.

I just tried to import jest-styled-components in my App.spec.js but that also doesn't seem to work.

This is my test now:

import React from 'react'
import App from './App'
import { MemoryRouter } from 'react-router-dom'
import 'jest-styled-components'
import { shallow } from 'enzyme'

describe('App Specs', () => {
  it('Should match with the snapshot for this component', () => {
    const wrapper = shallow(
      <MemoryRouter initialEntries={['/']}>
        <App />
      </MemoryRouter>
    )
    expect(wrapper).toMatchSnapshot()
  })
})

And this causes the following output when using yarn test:

 FAIL  src/App.spec.js
  ● App Specs › Should match with the snapshot for this component

    expect(value).toMatchSnapshot()

    Received value does not match stored snapshot 1.

    - Snapshot
    + Received

    @@ -29,11 +29,11 @@
             "canGo": [Function],
             "createHref": [Function],
             "entries": Array [
               Object {
                 "hash": "",
    -            "key": "s7iqm9",
    +            "key": "7c2oz3",
                 "pathname": "/",
                 "search": "",
                 "state": undefined,
               },
             ],
    @@ -43,11 +43,11 @@
             "index": 0,
             "length": 1,
             "listen": [Function],
             "location": Object {
               "hash": "",
    -          "key": "s7iqm9",
    +          "key": "7c2oz3",
               "pathname": "/",
               "search": "",
               "state": undefined,
             },
             "push": [Function],
    @@ -79,11 +79,11 @@
               "canGo": [Function],
               "createHref": [Function],
               "entries": Array [
                 Object {
                   "hash": "",
    -              "key": "s7iqm9",
    +              "key": "7c2oz3",
                   "pathname": "/",
                   "search": "",
                   "state": undefined,
                 },
               ],
    @@ -93,11 +93,11 @@
               "index": 0,
               "length": 1,
               "listen": [Function],
               "location": Object {
                 "hash": "",
    -            "key": "s7iqm9",
    +            "key": "7c2oz3",
                 "pathname": "/",
                 "search": "",
                 "state": undefined,
               },
               "push": [Function],

      at Object.it (src/App.spec.js:14:21)
          at Promise (<anonymous>)
      at Promise.resolve.then.el (node_modules/p-map/index.js:46:16)
          at <anonymous>

I really would prefer to stay unejected from CRA though...

What I'm still not very clear on is exactly which library (Jest, Enzyme, Enzyme-to-json, Styled Components, Jest Styled Components or maybe even React Router?) is causing this error. I have cross-posted this on Enzyme's repo as well: https://github.com/airbnb/enzyme/issues/1391. Maybe this will help someone.

the hashes certainly look like a styled component hash, however without knowing how you're rendering your App further down that causes the class hash to be in a "key" value of the location object then I don't think I can help further.

You might try creating a branch to eject into, if putting the jest-styled-components change there doesn't solve this then it's not likely a styled component issue.

Did some further digging and it seems to be related to Memory Router. When I shallow render a component that uses Styled Components but is not wrapped in Memory Router, it works.

Found this link as well that looks very similar to my case: https://swaac.tamouse.org/testing/2017/08/02/TIL-enzyme-shallow-render-with-memoryrouter-doesnt-work/

This is interesting, thanks @CoenWarmer @abharvey.
Given the MemoryRouter is breaking the snapshots with the random keys, I'm closing this since it's not related to this package.
Also, this comment describes a potential solution.

For anyone else still struggling with this (like I was 5 mins ago), I fixed by snapshotting the component instead of the parent <Router>

Try:

expect(wrapper.find(App)).toMatchSnapshot()

You can also pass key length through keyLength props and keep its value as 0. Ex: keyLength={0}. This will make sure, key doesn't change on each test run.

<MemoryRouter initialEntries={['/']} keyLength={0}>
   <App />
</MemoryRouter>
Was this page helpful?
0 / 5 - 0 ratings