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

Support convert shallow wrapper to format compatible with pretty-format and jest-snapshot #541

Conversation

rodrigopr
Copy link
Contributor

The new .json() method convert the shallow node to compatible object.

This allow us to use enzyme+shallow with jest-snapshot:

import Foo from 'yyy';

const Bar = ({ value }) => (
  <div className="in-bar">
    <Foo fooProp={value} />
  </div>
);

const wrapper = shallow(<Bar value={ 'xxx' } />);
expect(wrapper.json()).toMatchSnapshot();

will create a snapshot as:

exports[`example test 1`] = `
<div className="in-bar">
  <Foo fooProp="xxx" />
</div>
`;

More info: pretty-format and jest snapshot

The new `.json()` method convert the shallow node to
compatible object.

This allow us to use enzyme+shallow with jest-snapshot:

```
const Foo = () => <div className="in-foo" />;
const Bar = ({ value }) => (
  <div className="in-bar">
    <Foo fooProp={value} />
  </div>
);

const wrapper = shallow(<Bar value={ 'xxx' } />);
expect(wrapper.json())..toMatchSnapshot();
```

will create a snapshot as:

```
exports[`example test 1`] = `
<div className="in-bar">
  <Foo fooProp="xxx" />
</div>
`;
```
@aweary
Copy link
Collaborator

aweary commented Aug 12, 2016

👍 I think this is a great idea. This basically does what react-test-renderer does, correct? Right now there are some issues causing enzyme and react-test-renderer to be incompatible, so if this lets users take advantage of snapshots in Jest with just enzyme, that's perfect.

props: omit(this.props(), 'children'),
children: (childrens.length) ? childrens : null,
$$typeof: Symbol.for('react.test.json'),
};
Copy link
Collaborator

@aweary aweary Aug 12, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this format specified by jest-snapshot? If so, maybe we should call this snapshot() instead of json() to make it clear that this method is specifically for snapshot tests?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The format is defined by pretty-format (https://github.com/thejameskyle/pretty-format/blob/master/plugins/ReactTestComponent.js), which jest-snapshot uses.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably worth adding a // @see _URL_ directive for this function so we can maintain interop and know where to look to.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it might be worth only installing this method if jest is globally available? As it won't provide much benefit in other test runners currently.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about when Symbols aren't available?

Using a registry symbol is certainly a novel way to use globals, but I'm very loath to add one here.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we warn if they're calling json() in an environment without Symbol.for? It's available in Node since 0.11, and this wouldn't be very useful in a browser environment. That's another reason I think it should be called snapshot, since it's not meant to be used as a general-purpose JSON method.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it were, it would need to be called toJSON anyways. I agree that the name json isn't appropriate.

The catch is that enzyme, and its tests, need to run in many browsers and all supported nodes, if we want to be able to test how our React components actually work in these environments. Relying on Symbols hurts that goal.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, Symbol.for is an issue. pretty-format relies on it, though.

I agree now that the json() name can be confusing, I'm liking snapshot() more but usage will be a little weird: expect(wrapper.snapshot()).toMatchSnapshot() (or something similar)

@rodrigopr
Copy link
Contributor Author

rodrigopr commented Aug 12, 2016

Yes @aweary, it does the same thing that react-test-renderer toJSON

@blainekasten
Copy link
Contributor

Since this API awkwardly only is supported in one test environment, would it be a crazy and/or bad idea to effectively replicate jest#toMatchSnapshot in Enzyme?

import { matchesSnapshot, shallow } from 'enzyme';

const wrapper = shallow(<div />);

wrapper.matchesSnapshot(); // this is the assertion

We would then have to write to a file, have them commit it to source, and do compare with that file. I'm not sure if this is possible however, just spit-balling.

@ljharb
Copy link
Member

ljharb commented Aug 15, 2016

In general, I'm not a fan of snapshot testing. The whole point of enzyme is to NOT do snapshot testing - ie, jest-snapshot is a step in the wrong direction.

@rodrigopr
Copy link
Contributor Author

rodrigopr commented Aug 15, 2016

@blainekasten I think this can be better implemented in the test-runner/assertion framework, but I wouldn't mind changing to this approach if there is a feasible way to implement this here.

@ljharb I think snapshot is a good alternative in react testing ecosystem because it automatizes some (if not most) of the expected(...).to.xxx() we make today in our codebase. Also, IMO it makes breaking changes/bugs easier to catch by reviewing the snapshots outputs, but I'm interested in knowing more why you see it as a wrong direction (not sure this is the proper place).

Let me know if you think this would be better as an external lib, it doesn't rely on nothing internal to shallow wrapper right now.

@aweary
Copy link
Collaborator

aweary commented Aug 15, 2016

I think snapshot testing is great in general, but I can see why @ljharb might not be as excited about integrating it with Enzyme. Enzyme is a tool for traversing component structure, so adding snapshot testing would be moving away from that, making it more general purpose. I think ideally Enzyme would be used in conjunction with another tool that is responsible for snapshot testing--or it would be extended by an external lib.

Soon enough Enzyme will work fine together with react-test-renderer, which IMO might make this unnecessary.

@just-boris
Copy link
Contributor

I could set up snapshot testing with Enzyme right away, without any extra changes.
So the whole new thing seems redudant, but some existing methods can be polished to provide better experience. See my text bellow.

Let's create a test shallow component:

const component = shallow(<MyCmp title="Hello!" config={{test: 'config'}} />)

There are several options how to get a snapshot of it.

  1. Use ShallowWrapper.debug. This already-existing feature in Enzyme produces human readable JSX output, that can be saved as snapshot
expect(component.debug()).toMatchSnapshot();
//produces the following
exports[`test renders test component 1`] = `
"<div config={{...}}>
<h1>Hello!</h1>
<p>Test content</p>
</div>"
`;

But look the config prop has been replaced to {{...}} so we cannot check that it was not changed. It is behavior of .debug() function, so if it is not okay for you, look at the next solution.

  1. Get ReactElement instance. Enzyme gives you direct access to ReactElement via .get() function, that you can pass into jest and let pretty-format do the rest of job for you:
expect(component.get(0)).toMatchSnapshot();
//prints the following
exports[`test renders test component 2`] = `
<div config={
    Object { "test": "config" }
  }>
  <h1>Hello!</h1>
  <p>Test content</p>
</div>
`;

That's better, object is preserved, but it looks like private API usage. Do this or not is up to you, you can choose between this and option above.

  1. Finally, you can snapshot ShallowWrapper directly.
    As far as this is still Javascript object, it can be also serialized and saved as snapshot. But the output will be large and not readable, so I'd recommend you to look at the options above.

@rodrigopr
Copy link
Contributor Author

rodrigopr commented Oct 3, 2016

With ReactElement support in pretty-format (it was introduced after this PR) I think we can close this.

update:
Approach 2 from last comment don't work with child components from shallow render, at least when they are stateless function components.

For anyone interested in using enzyme with snapshot testing, there is a lib that provide shallowToJson implementation like this PR, and also support for render and mount wrapper: https://github.com/adriantoine/enzyme-to-json

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

Successfully merging this pull request may close these issues.

5 participants