Skip to content

ReactTestRenderer doesn't work with refs or ReactDOM.findDOMNode #7371

Closed

Description

@ryanseddon

Jest snapshot testing uses ReactTestRenderer but if my component contains a ref or uses ReactDOM.findDOMNode it throws TypeError: component.getPublicInstance is not a function.

Component

import React from 'react';

export default class Link extends React.Component {
  render() {
    return (
      <a
        ref={a => this._a = a}
        href={this.props.page || '#'}>
        {this.props.children}
      </a>
    );
  }
}

Test

'use strict'

import React from 'react';
import Link from '../Link';
import renderer from 'react-test-renderer';

describe('Link', () => {
  it('renders correctly', () => {
    const tree = renderer.create(
      <Link page="foo" />
    ).toJSON();

    expect(tree).toMatchSnapshot();
  });
});

stack trace

 FAIL  __tests__/Link-test.js (2.148s)
● Link › it renders correctly
  - TypeError: component.getPublicInstance is not a function
        at attachRef (node_modules/react/lib/ReactRef.js:20:19)
        at Object.ReactRef.attachRefs (node_modules/react/lib/ReactRef.js:42:5)
        at attachRefs (node_modules/react/lib/ReactReconciler.js:26:12)
        at CallbackQueue._assign.notifyAll (node_modules/react/lib/CallbackQueue.js:67:22)
        at ReactTestReconcileTransaction.ON_DOM_READY_QUEUEING.close (node_modules/react/lib/ReactTestReconcileTransaction.js:37:26)
        at ReactTestReconcileTransaction.Mixin.closeAll (node_modules/react/lib/Transaction.js:204:25)
        at ReactTestReconcileTransaction.Mixin.perform (node_modules/react/lib/Transaction.js:151:16)
        at batchedMountComponentIntoNode (node_modules/react/lib/ReactTestMount.js:61:27)
        at ReactDefaultBatchingStrategyTransaction.Mixin.perform (node_modules/react/lib/Transaction.js:138:20)
        at Object.ReactDefaultBatchingStrategy.batchedUpdates (node_modules/react/lib/ReactDefaultBatchingStrategy.js:63:19)

Activity

  1. zpao commented on Jul 29, 2016

    @zpao
    Contributor

    Thanks for the well documented issue! I'll leave it to @spicyj since he did the hard work on this.

  2. sophiebits commented on Jul 31, 2016

    @sophiebits
    Contributor

    Hm, after #7258 (which should be in 15.3.0) it give you null for any ref and should not throw. It's expected that ReactDOM.findDOMNode doesn't work.

  3. ryanseddon commented on Aug 1, 2016

    @ryanseddon
    ContributorAuthor

    So would it be safe to say that snapshot testing on something like react-frame-component wouldn't work?

  4. KnutHelland commented on Aug 5, 2016

    @KnutHelland

    Looks like React also logs a warning when using refs with react-test-renderer now:

    Warning: Stateless function components cannot be given refs (See ref "main" in a component created by DummyComponent). Attempts to access this ref will fail.
    

    caused by code similar to this example:

    const DummyComponent = React.createClass({
        render() {
            return <div ref="main">content</div>;
        }
    });

    (I can put up an example on github if you want)

  5. gaearon commented on Aug 7, 2016

    @gaearon
    Collaborator

    So would it be safe to say that snapshot testing on something like react-frame-component wouldn't work?

    It wouldn’t, but there are a few possible options:

    • You can mock it out (assuming you use Jest)
    • In the future test renderer could provide a first-class component mocking API out of the box, unrelated to Jest
    • We could give you a mock object that looks like a DOM node but doesn’t actually do anything

    Any solutions you prefer?

  6. traviscooperjs commented on Sep 1, 2016

    @traviscooperjs

    We had the same problem, but solved it using the ref callback as described (with examples) here.
    https://facebook.github.io/react/docs/more-about-refs.html#the-ref-callback-attribute

    componentWillMount: function() {
      this._refs = {};
    },
    componentDidMount: function() {
      this._refs[`textRefId`].focus();
    },
    render: function() {
      return <TextInput ref={(c) => this._refs[`textRefId`] = c; } />;
    },
  7. ryanseddon commented on Sep 1, 2016

    @ryanseddon
    ContributorAuthor

    @gaearon sorry missed your comment.

    We could give you a mock object that looks like a DOM node but doesn’t actually do anything

    That would be the preference as I'm asserting the children of the frame component.

  8. aweary commented on Sep 2, 2016

    @aweary
    Contributor

    I don't think providing a mock DOM node would be the best solution here. We'd have to implement the full DOM node API so that when it's actually used it doesn't throw. It would be better if we could have an optional integration with jsdom (or whatever the user chooses to use), so that we can use a full implementation of the DOM and avoid having to implement and maintain our own mock API.

  9. gaearon commented on Nov 16, 2016

    @gaearon
    Collaborator

    Refs work with test renderer in master, and you can even mock them to return something else instead of null:

    import React from 'react';
    
    export default class MyInput extends React.Component {
      componentDidMount() {
        this.input.focus();
      }
      render() {
        return (
          <input ref={node => this.input = node} />
        );
      }
    }
    import React from 'react';
    import MyInput from './MyInput';
    import renderer from 'react-test-renderer';
    
    function createNodeMock(element) {
      if (element.type === 'input') {
        return {
          focus() {},
        };
      }
      return null;
    }
    
    it('renders correctly', () => {
      const tree = renderer.create(
        <MyInput />,
        {createNodeMock}
      ).toJSON();
      expect(tree).toMatchSnapshot();
    });

    There are no plans to support findDOMNode() in test renderer because it should be agnostic of React DOM and there is no way to implement it in a way that won't break in the future.

  10. romanoff commented on Nov 28, 2016

    @romanoff

    For some reason I still can't use ref in jest tests. Details here: http://stackoverflow.com/questions/40852131/cant-get-ref-in-jest-tests

  11. gaearon commented on Nov 28, 2016

    @gaearon
    Collaborator

    @romanoff

    React test renderer is not coupled to React DOM. It can't "guess" which DOM APIs your component relies on. You need to mock the nodes yourself, as noted in 15.4.0 release notes. I hope this helps!

  12. mweststrate commented on Mar 10, 2017

    @mweststrate

    What is the recommended heuristic to detect that findDOMNode won't work, in order to take a different code path? (try / catch could work but isn't very clear)

    Edit: to elaborate: this is for a component library, so mocking (inside the library at least) is not an option

  13. hyzhak commented on May 31, 2017

    @hyzhak

    Is there any idea how to overcome this bug? I have few third party components (like https://github.com/ericgio/react-bootstrap-typeahead/) which uses ReactDOM.findDOMNode how could I use jest snapshots with them?

  14. a7madgamal commented on Jul 24, 2017

    @a7madgamal

    any help for the 10-thumbs-up comment will be really appreciated :D

  15. gaearon commented on Jul 24, 2017

    @gaearon
    Collaborator

    This is not a bug. As explained above it is intentional.

    The workaround is simple if you use jest. Just mock the third party component causing issues.

    For example:

    jest.mock('third-party-button', () => 'ThirdPartyButton');

    Put this at the top of your test file.

    Now any imports of third-party-button (replace this with your component) will become a string (e.g. ThirdPartyButton) so the component will become a “leaf” in the snapshot, like a div. Of course this won't actually test it, but it makes sense to only test your own code.

    For libraries exporting multiple components, the workaround is similar but you'd want to provide a different mock. Something like:

    jest.mock('third-party-lib', () => {
      return {
        ThirdPartyButton: 'ThirdPartyButton',
        OtherThing: 'OtherThing',
      };
    }));

    Finally, as last option, you can mock react-dom itself.

    jest.mock('react-dom', () => ({
      findDOMNode: () => ({}),
    });

    Note you can return anything there and adjust it to match the expectations of the tested code.

    All of this works if you use Jest. Unfortunately I don’t have good solutions for other runners.

  16. a7madgamal commented on Jul 24, 2017

    @a7madgamal

    Thanks a lot for this helpful comment 👍🏻
    I'm sure your explanation will be helpful to many jest noobz like myself!

  17. ktj commented on Jul 31, 2017

    @ktj

    This is how I mocked 3rd party dependency that used react-dom findDOMNode in my app.

    /// SomeComponent.test.js
    jest.mock('some-library/lib/MockThis', () => {
      const Mock = require.requireActual('./MockForMockThis');
      return Mock;
    });
    
    // test....
    

    Drawback in this is that if that 3rd party dependency changes diretory structure etc, then this will break.

  18. gurre commented on Aug 19, 2017

    @gurre

    Mocking refs and canvas was easy using:

    jest.mock('react-dom', () => ({
        findDOMNode: () => ({
          getContext: jest.fn(),
        }),
      })
    );
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions