Skip to content
New issue

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

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

Already on GitHub? # to your account

Allow ReactShallowRenderer to expose JSX for test debugging #4835

Closed
johnnyreilly opened this issue Sep 10, 2015 · 14 comments
Closed

Allow ReactShallowRenderer to expose JSX for test debugging #4835

johnnyreilly opened this issue Sep 10, 2015 · 14 comments

Comments

@johnnyreilly
Copy link

Hi,

I've been making use of ReactShallowRenderer for testing. I really like it. You mention in your docs that you would appreciate the React community's feedback on how it should evolve; here's a suggestion!

The one problem I have is when it comes to investigating failing tests.

First a little digression. I'm just using vanilla Jasmine for my testing. When it comes to deep objects not matching up I've been using a custom matcher I wrote called toEqualAsObject:

import jsondiffpatch from 'jsondiffpatch';

const jdp = jsondiffpatch.create({
  arrays: {
    detectMove: true
  }
});

const customMatchers = {
  toEqualAsObject(util, customEqualityTesters) {
    return {
      compare(actual, expected) {
        const result = {
          pass: util.equals(actual, expected, customEqualityTesters)
        };

        if (!result.pass) {
          const delta = jdp.diff(actual, expected);
          result.message = `The objects were different.

Expected:

${ JSON.stringify(expected, null, 2) }

Actual:
${ JSON.stringify(actual, null, 2) }

Difference:
${ JSON.stringify(delta, null, 2) }

Difference format info can be found here: https://github.com/benjamine/jsondiffpatch/blob/master/docs/deltas.md
`;
        }

        return result;
      }
    };
  }
};

export default customMatchers;

This matcher builds on jsondiffpatch to help you to isolate where the difference between 2 objects lie. This is really useful for general testing but doesn't help particularly when it comes to JSX testing like this:

result = renderer.getRenderOutput();
expect(result.type).toBe('div');
expect(result.props.children).toEqual([
  <span className="heading">Title</span>,
  <Subcomponent foo="bar" />
]);

The reason is, a subtle difference in JSX can result in a fairly different objects being constructed. Comparing them is not massively revealing and investigation can take some time. I've a suggestion: It would be really handy if you could extract a JSX representation of an object from the renderer output. What I'm imagining is the ability to do something like this:

result.props.children.asJSX();

Which would would produce a string like this:

"
  <span className="heading">THE WRONG TITLE</span>
  <Subcomponent foo="bar" />
"

This would allow the writing of a custom matcher to be used when comparing JSX which would, upon failure, use the asJSX() to report a helpful error message. I've spent more time than I would like digging through differences in object structure when investigating failing tests and think that a feature like this could be a real win.

What do you think?

@vvo
Copy link

vvo commented Oct 9, 2015

Yes investigating JSX diffs while using expect for example is currently a bit tricky. I always go to debugger.

@gaearon
Copy link
Collaborator

gaearon commented Oct 9, 2015

Rather than bake this into React, it seems like something a custom test/assertion reporter could do.
In fact I'm convinced I saw this on Twitter but I can't recall the project name.

@vvo
Copy link

vvo commented Oct 9, 2015

There's one for jasmine: https://github.com/rogeriochaves/jasmine-react-diff, I am currently trying to understand how this could be ported to expect + mocha for instance (mjackson/expect#37)

@gaearon
Copy link
Collaborator

gaearon commented Oct 9, 2015

See also:

https://www.npmjs.com/package/inspect-react-element
https://jamesfriend.com.au/better-assertions-shallow-rendered-react-components

Additionally, you can make the output of failing tests a bit easier to read by pretty-printing the ReactElements which are involved, for which you could use a module I wrote called inspect-react-element.

Now when an assertion fails, like:

expect(renderedTree).toContainReactNodeInTreeLike(<PageHeader nonExistentProp />);

You would see the message:

Expected
 <div>
   <div className="page-head">
     <PageHeader title={undefined} unimportant="something" />
   </div>
   <div className="page-body" />
 </div>
to contain a ReactNode in its tree like
 <PageHeader nonExistentProp={true} />

Here's the implementation of the custom toContainReactNodeInTreeLike Jasmine/Jest matcher used above, which prints an informative error message when a match is not found:

jasmine.addMatchers({
 toContainReactNodeInTreeLike(expectedChild) {
   const {actual, isNot} = this;
   this.message = () =>
     clean`
       Expected
       ${indent(inspectReactElement(actual), 1)}
       ${isNot ? 'not ' : ''}to contain a ReactNode in its tree like
       ${indent(inspectReactElement(expectedChild), 1)}
     `;

   const found = findAllMatching(actual, expectedChild);
   return found.length > 0;
 },
});

In conclusion, try to write your shallow-render tests the way you'd write tests using TestUtils.findAllInRenderedTree etc, but use utilities which do the same thing for shallow-rendered trees as the TestUtils do for rendered DOM components.

@gaearon
Copy link
Collaborator

gaearon commented Oct 10, 2015

@vvo
Copy link

vvo commented Oct 10, 2015

Awesome thanks for curating this @gaearon, will be useful to me, I am going to implement it in expect

@vvo
Copy link

vvo commented Oct 10, 2015

Hmm unexpected seems really nice, thanks a lot

@johnnyreilly
Copy link
Author

This looks fantastic - thanks so much! I will be investigating all of the above. 👍

@vvo
Copy link

vvo commented Oct 16, 2015

So I did:

And I get (mocha):
2015-10-16-002254_418x344_scrot

It was cool, hope this will be usable for a lot of other people. Since I have been testing with react shallow renderer, comparing using toEqualJSX is a joy.

We will write a blog post soon about all this.

@juliankrispel
Copy link

this has been a pain point, thanks so much 👍

@vvo
Copy link

vvo commented Oct 16, 2015

@juliankrispel share the ❤️ then please ;)

@juliankrispel
Copy link

you bet @vvo

@gaearon
Copy link
Collaborator

gaearon commented Oct 3, 2017

Jest is using https://github.com/facebook/jest/tree/master/packages/pretty-format which supports printing React elements.

import prettyFormat from 'pretty-format';
const {ReactElement, ReactTestComponent} = prettyFormat.plugins;

// in a test
const renderer = // ...
// ...
const element = renderer.getRenderOutput();

const formatted = prettyFormat(element, {
  plugins: [ReactElement],
  printFunctionName: false,
});

/*
<button
  onClick=[Function]
>
  Hello World
</button>
*/

@gaearon gaearon closed this as completed Oct 3, 2017
@vvo
Copy link

vvo commented Oct 7, 2017

Yes, we just use jest snapshots nowadays :)

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

No branches or pull requests

4 participants