Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jest snapshots change from v6 to v7 #1245

Closed
bensampaio opened this issue Apr 17, 2019 · 14 comments
Closed

Jest snapshots change from v6 to v7 #1245

bensampaio opened this issue Apr 17, 2019 · 14 comments

Comments

@bensampaio
Copy link

Do you want to request a feature or report a bug?

Bug

What is the current behavior?

When generating shallow snapshots of react components with react-redux@6, if there is a connected component then its name looks like Connect(ComponentName). However, with react-redux@7, all connected component names look like Memo(ConnectFunction).

If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem. Your bug will get fixed much faster if we can run your code and it doesn't have dependencies other than React. Paste the link to a CodeSandbox (https://codesandbox.io/s/new) or RN Snack (https://snack.expo.io/) example below:

If you run the tests in this sandbox you'll see that the ConnectedButton component shows up as UNDEFINED in the snapshot. This is not the same behavior I have locally but it is also not what I would expect. I cannot determine the reason for the different behavior, maybe some dependencies from react-scripts are not up to date?

What is the expected behavior?

I would expect the component name to be something like: Memo(Connect(ComponentName)).

Which versions of React, ReactDOM/React Native, Redux, and React Redux are you using? Which browser and OS are affected by this issue? Did this work in previous versions of React Redux?

react@16.8.6
react-dom@16.8.6
react-redux@7.0.2
redux@4.0.1

@timdorr
Copy link
Member

timdorr commented Apr 17, 2019

This isn't a react-redux bug. We use the newer React.memo() component wrapper for our connected component. Something about Jest's snapshots aren't recording this the way you would like. But we're just using a React API, we can't do anything differently to fix this. You should consult the support resources for Jest.

@timdorr timdorr closed this as completed Apr 17, 2019
@ismay
Copy link

ismay commented May 13, 2019

@bensampaio Did you manage to resolve this? I ran into this as well, it makes the snapshots less useful, as now all I know is that there's a connected component being rendered, but not which connected component.

@timdorr I can see how you wouldn't call this a bug, as I imagine you don't consider this part of this package's api, but it is a common approach to testing components. And I wonder if it can be fixed outside of this lib without abandoning snapshot tests. There's no way that this could be fixed in this lib? I remember styled components having something similar for example, and I believe they addressed it from within styled-components as well.

@bensampaio
Copy link
Author

@ismay No, I didn't manage to resolve this. I looked into the jest code for the memo function but I couldn't figure out how to fix this. I am also not sure if this has to be fixed here or in the jest repo.

@ismay
Copy link

ismay commented May 13, 2019

I guess a separate jest snapshot serializer could resolve this. It's quite an investment though, so if it could easily be resolved from react-redux that would be nice of course.

@ismay
Copy link

ismay commented May 13, 2019

What you could do is create a custom serializer like so:

import pretty from 'pretty'

const test = val => {
    const isReactWrapper = val.constructor.name === 'ReactWrapper'
    return isReactWrapper
}

const print = depth => wrapper => {
    const hasChildren = wrapper.exists()
    const name = wrapper.name()
    const childElements = hasChildren && wrapper.children().getElements()

    if (childElements.length === 0) {
        return (
            `<${name}>
                ${wrapper.text()}
            </${name}>`
        )
    }

    if (!hasChildren || depth === 0) {
        return `<${name} />`
    }

    return pretty(
        `<${name}>
            ${wrapper.children().map(print(depth - 1)).join('')}
        </${name}>`
    )
}

const createSerializer = depth => ({
    print: wrapper => print(depth)(wrapper.children()),
    test,
})

module.exports = createSerializer

Import the serializer for the component tests for which you want to use it, and register it with addSnapshotSerializer, specifying the depth that you want to serialize to. You'll have to mount the component:

import React from 'react'
import createSerializer from '../../test/serializer'
import { mount } from 'enzyme'
import App from './App'

expect.addSnapshotSerializer(createSerializer(2))

describe('<App/>', () => {
    it('renders the expected tree', () => {
        const tree = mount(<App />)
        expect(tree).toMatchSnapshot()
    })
})

Which will serialize to a snapshot with component names to the specified depth (including the connected component's names that you want, if you set the depth correctly). Modify the code if you want to serialize props as well, etc.

@HendrikThePendric
Copy link

HendrikThePendric commented May 13, 2019

As an alternative to the excellent suggestion by @ismay, you could also mock the connect function, like so:

jest.mock('react-redux', () => ({
    connect: jest.fn(() => component => `Connected${component.name}`),
}))

Only caveat is that the component does need to have a name.

@ismay
Copy link

ismay commented May 13, 2019

Nice, I prefer that to my solution actually.

@bensampaio
Copy link
Author

That's indeed a good idea. I was already thinking about creating a mock anyway, so this one more reason to do it.

@pawelw
Copy link

pawelw commented Jul 31, 2019

I hit the same issue recently. I think the issue could exist within this repo. Other components that I export with

export default React.memo(MyComponentName);

renders as expected in jest snapshots, which is:

<Memo(MyComponentName) />

I dived into the code a little bit and noticed that when I set component purity to be false, and memo is ignored then value of Connect.displayName renders in place of MyComponentName. However when React.memo is used within connect function, Connect.displayName is omitted completely, and <Memo(ConnectFunction) /> renders in snapshot.

Not sure if this could be the reason however but seems related.

@rodoabad
Copy link

Do we have ay resolution or guidance on this one moving forward?

@rodoabad
Copy link

rodoabad commented Sep 13, 2019

My snapshots are like this now.

Click to update snapshot for 'should pass props from parent component' test

expect(received).toMatchSnapshot()

Snapshot name: `Given the connected component should pass props from parent component 1`

- Snapshot
+ Received

- <MockComponent
-   accountId=""
-   cancelPlacedOrder={[Function]}
-   clearOrderDetails={[Function]}
-   closeCancelModal={[Function]}
-   confirmOrder={[Function]}
-   confirmOrderState={
-     Struct {
-       "isCancelModalOpen": true,
-       "transactionId": "%EbMEFX4kS%",
-     }
-   }
-   extraProps="aQ^!Etpxrr[L^*M5f8"
-   openCancelModal={[Function]}
-   resetServerError={[Function]}
-   shouldDisplayReviewMessage={false}
-   store={
-     Object {
-       "clearActions": [Function],
-       "dispatch": [Function],
-       "getActions": [Function],
-       "getState": [Function],
-       "replaceReducer": [Function],
-       "subscribe": [Function],
+ <ContextProvider
+   value={null}
+ >
+   <MockComponent
+     accountId=""
+     cancelPlacedOrder={[Function]}
+     clearOrderDetails={[Function]}
+     closeCancelModal={[Function]}
+     confirmOrder={[Function]}
+     confirmOrderState={
+       Struct {
+         "isCancelModalOpen": false,
+         "transactionId": "Z$KoBY",
+       }
      }
-   }
-   storeSubscription={
-     Subscription {
-       "listeners": Object {
-         "clear": [Function],
-         "get": [Function],
-         "notify": [Function],
-         "subscribe": [Function],
-       },
-       "onStateChange": [Function],
-       "parentSub": undefined,
-       "store": Object {
+     extraProps="A&HHZ4Y&Ps"
+     openCancelModal={[Function]}
+     resetServerError={[Function]}
+     shouldDisplayReviewMessage={false}
+     store={
+       Object {
          "clearActions": [Function],
          "dispatch": [Function],
          "getActions": [Function],
          "getState": [Function],
          "replaceReducer": [Function],
          "subscribe": [Function],
-       },
-       "unsubscribe": [Function],
+       }
      }
-   }
-   updateOrderDetails={[Function]}
- />
+     updateOrderDetails={[Function]}
+   />
+ </ContextProvider>
<Click to see difference>

Error: expect(received).toMatchSnapshot()

Snapshot name: `Given the connected component should pass props from parent component 1`

- Snapshot
+ Received

- <MockComponent
-   accountId=""
-   cancelPlacedOrder={[Function]}
-   clearOrderDetails={[Function]}
-   closeCancelModal={[Function]}
-   confirmOrder={[Function]}
-   confirmOrderState={
-     Struct {
-       "isCancelModalOpen": true,
-       "transactionId": "%EbMEFX4kS%",
-     }
-   }
-   extraProps="aQ^!Etpxrr[L^*M5f8"
-   openCancelModal={[Function]}
-   resetServerError={[Function]}
-   shouldDisplayReviewMessage={false}
-   store={
-     Object {
-       "clearActions": [Function],
-       "dispatch": [Function],
-       "getActions": [Function],
-       "getState": [Function],
-       "replaceReducer": [Function],
-       "subscribe": [Function],
+ <ContextProvider
+   value={null}
+ >
+   <MockComponent
+     accountId=""
+     cancelPlacedOrder={[Function]}
+     clearOrderDetails={[Function]}
+     closeCancelModal={[Function]}
+     confirmOrder={[Function]}
+     confirmOrderState={
+       Struct {
+         "isCancelModalOpen": false,
+         "transactionId": "Z$KoBY",
+       }
      }
-   }
-   storeSubscription={
-     Subscription {
-       "listeners": Object {
-         "clear": [Function],
-         "get": [Function],
-         "notify": [Function],
-         "subscribe": [Function],
-       },
-       "onStateChange": [Function],
-       "parentSub": undefined,
-       "store": Object {
+     extraProps="A&HHZ4Y&Ps"
+     openCancelModal={[Function]}
+     resetServerError={[Function]}
+     shouldDisplayReviewMessage={false}
+     store={
+       Object {
          "clearActions": [Function],
          "dispatch": [Function],
          "getActions": [Function],
          "getState": [Function],
          "replaceReducer": [Function],
          "subscribe": [Function],
-       },
-       "unsubscribe": [Function],
+       }
      }
-   }
-   updateOrderDetails={[Function]}
- />
+     updateOrderDetails={[Function]}
+   />
+ </ContextProvider>

ContextProvider is now wrapping all connected components.

@markerikson
Copy link
Contributor

That matches how connect works in v7, yes.

I don't think there is any real "guidance" to be offered here. The implementation of connect has changed over time. Snapshots that try to capture the component tree inside of connect are therefore likely to change as well based on the implementation. There's nothing for us to "fix" here.

@c0d3s
Copy link

c0d3s commented Nov 26, 2019

from the looks of it, this should be fixed in jest v25 jestjs/jest#9216 (comment)

@nabilfreeman
Copy link

Yes! This is fixed in jest 25. React Native 0.61 ships with 24 which is why my snapshots broke after upgrading.

I finally got here after a lot of Googling. Mystery solved!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants