draft-js icon indicating copy to clipboard operation
draft-js copied to clipboard

Advice on snapshot testing with Jest

Open paulyoung opened this issue 9 years ago • 19 comments

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

I suppose this could be considered a feature request to make data-editor and data-offset-key attribute values deterministic via some seed.

What is the current behavior?

When testing a component which wraps an Editor using the snapshots feature in Jest, the test always fails because certain attributes appear to be randomly generated and unique per instance.

This is a sample of the output from the test failure:

-             data-editor="4or1r"
-             data-offset-key="9r3en-0-0">
+             data-editor="cbctb"
+             data-offset-key="3jgg4-0-0">
              <div
                className="public-DraftStyleDefault-block public-DraftStyleDefault-ltr"                                                                                    
-               data-offset-key="9r3en-0-0">
+               data-offset-key="3jgg4-0-0">
                <span
-                 data-offset-key="9r3en-0-0"
+                 data-offset-key="3jgg4-0-0"

If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem.

The test is very simple:

import React from 'react';
import renderer from 'react-test-renderer';
import { MyEditor } from '../src';

jest.mock('react-dom');

describe('MyEditor', () => {
  it('renders as expected', () => {
    const component = renderer.create(<MyEditor />);
    const tree = component.toJSON();
    expect(tree).toMatchSnapshot();
  });
});

Where MyEditor wraps Editor and provides a default value for editorState.

What is the expected behavior?

I would hope to somehow still be able to use snapshot testing.

Which versions of Draft.js, and which browser / OS are affected by this issue? Did this work in previous versions of Draft.js?

I'm using Draft 0.9.1 and Jest 15.1.1

paulyoung avatar Oct 05 '16 23:10 paulyoung

+1

sugarshin avatar Oct 10 '16 05:10 sugarshin

@cpojer - would you mind chiming in here on whether consumers will need to ignore specific properties or if Draft itself should communicate that some properties should be ignored during snapshot testing

davidchang avatar Oct 14 '16 00:10 davidchang

there isn't any way right now to ignore this. The right solution is usually to mock out whatever creates randomness, like Date.now = jest.fn(() => 42) for example. What provides these random IDs and can we mock them?

cpojer avatar Oct 14 '16 02:10 cpojer

Perhaps I'm misunderstanding how react-test-renderer and/or snapshots work, but I thought the test renderer did a shallow render.

In that case, I don't know why I'm seeing internals of the Editor component. Ideally, I wouldn't care about the implementation details of Editor and only how my component was being rendered, which I thought was the point of shallow rendering.

paulyoung avatar Oct 14 '16 17:10 paulyoung

(This is a obviously a separate question to the original issue)

paulyoung avatar Oct 14 '16 17:10 paulyoung

No, it's not shallow, it's a normal render.

cpojer avatar Oct 15 '16 00:10 cpojer

However, you can mock out lower level components like jest.mock('path/to/Editor', () => 'Editor').

cpojer avatar Oct 15 '16 00:10 cpojer

What was the final solution to this problem?

tleunen avatar Dec 22 '16 00:12 tleunen

Was there any solution to this?

Edit: Using what @cpojer recommended, I was able to stub out the component that was rendering DraftJS to render just the component name.

kawikadkekahuna avatar Feb 04 '17 04:02 kawikadkekahuna

@kawikadkekahuna can you show your test code please ?

dadtmt avatar Feb 21 '17 11:02 dadtmt

I'm not sure if this is the same problem other people are having, but I had an issue where EditorState was being passed into my editor component and within it contained a random key that would be different on every jest run.

Mocking this function solved it for me:

jest.mock('draft-js/lib/generateRandomKey', () => () => '123');

neurosnap avatar Feb 28 '17 21:02 neurosnap

There are two issues happening here for snapshots:

  • Not mocking the draftjs Editor (which has random keys in data attributes)
  • Using EditorState.createWithContent() in props, which has random key values in the object

Fortunately, both of these issues are solved with @neurosnap's solution of mocking generateRandomKey inside draftjs.

This is what @cpojer's meant by:

The right solution is usually to mock out whatever creates randomness

Just to make this crystal clear, both issues will affect the example component below (which is fixed with the solution above):


import { Editor, EditorState } from 'draft-js';


class MyEditor extends React.PureComponent {
  constructor(props) {
    super(props);

    const initialState = EditorState.createEmpty();  // object contains random keys

    this.state = {
      initialState,
    };
  }

  render() {
    return <Editor editorState={this.state.initialState} />;   // Editor contains random data attributes
  }
}

export default Editor;

rickhanlonii avatar Mar 17 '17 17:03 rickhanlonii

Hey guys, because this issue is still open I'm just going to write my proposal here. So I am doing integration tests with jest. I want to test our sign up endpoint which will return a json result that looks something like this:

{
ok: true,
token: randomTokenHere,
user_id: randomUserId
}

My test is something like this:

test('sign up new user', () => {
  return rpap.post({
    url: '/users.signup',
    body: {
      email: '[email protected]',
      password: 'jestit',
      first_name: 'Jest',
      last_name: 'Jesting',
    },
  })
    .then((result) => {
      expect(result).toMatchSnapshot();
    });
});

It would be cool if I can do that:

      expect(result).ignore({
        token,
        user_id,
      }).toMatchSnapshot();

Or because this can lead to mistakes it's going to be better if we can validate that it is at least there but with don't care what the value is because it's random.

What I have to do now is delete the values from the result before testing it. That's fine but if jest can do this for me in a nice syntax and even validate it in a non strict manner it's going to be epic.

Essentially the snapshot files will have only the deterministic values inside.

What are your thoughts about this?

thinklinux avatar Oct 02 '17 12:10 thinklinux

I like that! Seems like a great use case for a custom matcher

rickhanlonii avatar Oct 02 '17 20:10 rickhanlonii

Is the problem solved?

piyaleemaiti avatar Jan 22 '20 11:01 piyaleemaiti

I also had to do this (entity map was still filled with uuids):

jest.mock("draft-js/lib/uuid", () => {
    let idUUID = 0;
    return () => (++idUUID).toString();
});

zuffik avatar Oct 16 '20 07:10 zuffik

In Jest 23 we added a snapshot property matcher for this use case:

it('will check the matchers and pass', () => {
  const user = {
    createdAt: new Date(),
    id: Math.floor(Math.random() * 20),
    name: 'LeBron James',
  };

  expect(user).toMatchSnapshot({
    createdAt: expect.any(Date),
    id: expect.any(Number),
  });
});

// Snapshot
exports[`will check the matchers and pass 1`] = `
Object {
  "createdAt": Any<Date>,
  "id": Any<Number>,
  "name": "LeBron James",
}
`;

This is better than replacing or ignoring the value because you can assert that it matches some structure (like a string).

I'm not sure if this has been extended to support snapshot testing of components, but there's no reason it could be.

rickhanlonii avatar Oct 16 '20 16:10 rickhanlonii

We encountered a similar issue, yet when instantiating the editor with a preexisting value:

describe('Editor', () => {
  it('renders previously breaking markup successfully', () => {
    const tree = renderer.create(<Editor value={markup} />).toJSON();
    expect(tree).toMatchSnapshot();
  });
});

we noticed that we needed to blend @neurosnap's suggestion with @zuffik's one as otherwise the editor would not contain the preexisting value — the deterministic "random" keys would always be the same (123) and overwrite themselves.

This is the core of the end result on our end:

jest.mock('draft-js/lib/generateRandomKey', () => {
  let deterministicKey = 0;
  return () => deterministicKey++;
});

Thank you all for getting us here. ✌🏼

mariusbutuc avatar Apr 12 '21 19:04 mariusbutuc

In the latest version of DraftJS the above guidance was not working for us. We opted instead to adjust the rendered DocumentFragment after-the-fact to make it deterministic and to avoid dependence on DraftJS internals. It was more code, but it works across major & minor version changes.

rbevers avatar Jun 24 '22 23:06 rbevers