title: Can't get React Forwarded refs to work in testing environment
body: <p>I have an issue where I want to test some forwarded refs. I am able to get these forwarded refs to work fine in my browser, but in the test environment I see a different behavior. </p>
  
  <p>You can browse my complete reproduction app here <a href="https://github.com/jasonfb/jest-playground-2" rel="nofollow noreferrer">https://github.com/jasonfb/jest-playground-2</a></p>
  
  <p>Quick overview:</p>
  
  <p>// App.js</p>
  
  <pre><code>import React from 'react';
  import './App.css';
  
  import AbcContainer from './abc_container'
  
  function App() {
    return (
      &lt;div className="App"&gt;
        &lt;AbcContainer /&gt;
      &lt;/div&gt;
    );
  }
  export default App;
  
  </code></pre>
  
  <p>// abc_container.js</p>
  
  <pre><code>import React from 'react'
  import DefThing from './def_thing'
  import styled from 'styled-components'
  
  const StyledAbcContainer = styled.div`
    display: block;
    width: 80%;
    height: 80%;
    margin-left: auto;
    margin-right: auto;
    border: solid 1px red;
    padding: 15px;
  `
  
  class AbcContainer extends React.Component {
    constructor(props) {
      super()
      this.def_thing_ref = React.createRef()
      this.state = {
        visible: false,
        message: "wait for it..."
      }
    }
  
    componentDidMount() {
      setTimeout(() =&gt; {
        console.log("this.def_thing_ref= ", this.def_thing_ref)
        let rect = this.def_thing_ref.current.getBoundingClientRect()
        console.log("the obejct's rect is ", rect)
        this.setState({visible: true,
            message: "the position of the box is " + rect.x + "," + rect.y +
            "I know this because the ref for this.def_thing_ref.current is populated"
        })
      }, 2500)
    }
  
  
    render() {
      const {visible, message} = this.state
  
      return (
        &lt;StyledAbcContainer data-testid="abc-container"&gt;
          StyledAbcContainer
          &lt;DefThing ref={this.def_thing_ref} visible={visible}/&gt;
  
          {message}
        &lt;/StyledAbcContainer&gt;
      )
    }
  
  }
  export default AbcContainer
  </code></pre>
  
  <p>// def_thing.js</p>
  
  <pre><code>import React, {Component} from 'react'
  import styled from "styled-components";
  
  const StyledDefThing = styled.div`
    position: relative;
    height: 110px; 
    width: 136px;
    border: solid 1px green;
    display: block;
  `
  class DefThing extends React.Component {
    render () {
      const {selfRef, visible} = this.props
      return (
        &lt;StyledDefThing ref={selfRef} style={{'visibility': visible ? 'visible' : 'hidden'}}&gt;
          StyledDefThing
        &lt;/StyledDefThing&gt;
      )
    }
  }
  export default React.forwardRef((props, ref) =&gt; &lt;DefThing {...props} selfRef={ref}  /&gt;)
  </code></pre>
  
  <p>Other than that, this is the basic create-react-app, with a few dependancies</p>
  
  <p>// package.json</p>
  
  <pre><code>{
    "name": "jest-playground-2",
    "version": "0.1.0",
    "private": true,
    "dependencies": {
      "@testing-library/dom": "^6.10.1",
      "@testing-library/jest-dom": "^4.2.4",
      "@testing-library/react": "^9.3.3",
      "@testing-library/user-event": "^7.1.2",
      "react": "^16.12.0",
      "react-dom": "^16.12.0",
      "react-scripts": "3.3.0",
      "styled-components": "^4.4.1"
    },
    "scripts": {
      "start": "react-scripts start",
      "build": "react-scripts build",
      "test": "react-scripts test",
      "eject": "react-scripts eject"
    },
    "eslintConfig": {
      "extends": "react-app"
    },
    "browserslist": {
      "production": [
        "&gt;0.2%",
        "not dead",
        "not op_mini all"
      ],
      "development": [
        "last 1 chrome version",
        "last 1 firefox version",
        "last 1 safari version"
      ]
    },
    "jest": {
      "coverageReporters": [
        "html",
        "text"
      ]
    }
  }
  </code></pre>
  
  <p>You can browse my complete reproduction app here <a href="https://github.com/jasonfb/jest-playground-2" rel="nofollow noreferrer">https://github.com/jasonfb/jest-playground-2</a></p>
  
  <p>In development, while in my browser, the forwarded ref works. After 2.5 second (arbitrary), the code in AbcContainer picks up the positioning of the DefThing by using the ref to it, and getting '.getBoundingClientRect()` on it, like so:</p>
  
  <p><code>this.def_thing_ref.current.getBoundingClientRect()</code></p>
  
  <p>(see abc_container.js #componentDidMount)</p>
  
  <p>in my browser, this works correctly and the <code>current</code> of the forwarded ref is populated</p>
  
  <p><a href="https://i.stack.imgur.com/nnzsD.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/nnzsD.png" alt="enter image description here"></a></p>
  
  <p>However, I Can't get this to work when running the specs. Here are my specs</p>
  
  <p>// abc_container.test.js</p>
  
  <pre><code>import React from 'react';
  import { render, wait, waitForElement } from '@testing-library/react';
  import App from './App';
  import AbcContainer from "./abc_container";
  
  
  
  test('it is able to render', () =&gt; {
    const {getByTestId} = render(&lt;AbcContainer/&gt;)
    const abcContainterElement = getByTestId('abc-container')
    expect(abcContainterElement).toBeInTheDocument()
  
  })
  
  
  test('after 1 second, the ref is created', async () =&gt; {
    const {getByText, getByTestId} = render(&lt;AbcContainer/&gt;)
    const abcContainerElement = getByTestId('abc-container')
    await wait(() =&gt; getByText(/the position of the box/i))
    const words = getByText(/the position of the box/i)
    expect(words).toBeInTheDocument()
  })
  </code></pre>
  
  <p>// App.test.js</p>
  
  <pre><code>import React from 'react';
  import { render } from '@testing-library/react';
  import App from './App';
  
  test('renders learn react link', () =&gt; {
    const { getByText } = render(&lt;App /&gt;);
    const styledDefThingElement = getByText(/StyledDefThing/i);
    expect(styledDefThingElement).toBeInTheDocument();
  });
  </code></pre>
  
  <p>// <code>def_thing.test.js</code></p>
  
  <pre><code>import React from 'react';
  import { render } from '@testing-library/react';
  import App from './App';
  
  import DefThing from './def_thing'
  
  test('renders a box and expects a ref', () =&gt; {
  
    const fakeRef = React.createRef()
    const { getByText } = render(&lt;DefThing ref={fakeRef}/&gt;);
  
  });
  
  </code></pre>
  
  <p>Unfortunately, I am getting this failure in abc_container which demonstrates the problem:</p>
  
  <pre><code>FAIL  src/abc_container.test.js (7.19s)
    ● Console
  
      console.log src/abc_container.js:32
        this.def_thing_ref=  { current: null }
      console.error node_modules/@testing-library/react/dist/act-compat.js:52
        Error: Uncaught [TypeError: Cannot read property 'getBoundingClientRect' of null]
            at reportException (/Users/jason/Work/LEARNING/React_JS/jest-playground-2/node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/living/helpers/runtime-script-errors.js:66:24)
            at Timeout.callback [as _onTimeout] (/Users/jason/Work/LEARNING/React_JS/jest-playground-2/node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/browser/Window.js:680:7)
            at listOnTimeout (timers.js:327:15)
            at processTimers (timers.js:271:5) TypeError: Cannot read property 'getBoundingClientRect' of null
            at setTimeout (/Users/jason/Work/LEARNING/React_JS/jest-playground-2/src/abc_container.js:33:45)
            at Timeout.callback [as _onTimeout] (/Users/jason/Work/LEARNING/React_JS/jest-playground-2/node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/browser/Window.js:678:19)
            at listOnTimeout (timers.js:327:15)
            at processTimers (timers.js:271:5)
  
    ● after 1 second, the ref is created
  
  </code></pre>
  
  <p>As you can see, <code>this.def_thing_ref</code> is returning <code>{ current: null }</code> which it should not be. In the real browser it returns <code>{current: div.sc-bVVdaja.hwwsLL}</code> which indicates that the ref works correclty</p>
  
  <p>it seems that there is just something missing about getting Forwarded Refs to work in the test setup</p>
  
  <p>UPDATE 12/13/2019:</p>
  
  <p>I made a little progress on this. I think I wasn't including ReactDOM in my test files. my new test files look like</p>
  
  <p>//def_thing.test.js</p>
  
  <pre><code>import React from 'react';
  
  import ReactDOM from 'react-dom';
  import { render } from '@testing-library/react';
  import { getByTestId, getByText } from '@testing-library/dom'
  
  import App from './App';
  
  import DefThing from './def_thing'
  
  test('renders a box and expects a ref', () =&gt; {
    const div = document.createElement('div');
  
    const fakeRef = React.createRef()
    const { getByText } = render(&lt;DefThing ref={fakeRef}/&gt;, div);
  
  });
  </code></pre>
  
  <p>//abc_container.test.js</p>
  
  <pre><code>import React from 'react';
  import ReactDOM from 'react-dom';
  import { render, wait, waitForElement } from '@testing-library/react';
  import { getByTestId, getByText } from '@testing-library/dom'
  
  import App from './App';
  import AbcContainer from "./abc_container";
  
  
  
  test('it is able to render',  () =&gt; {
    const div = document.createElement('div');
  
    const {getByTestId} = render(&lt;AbcContainer/&gt;, div)
    const abcContainterElement = getByTestId('abc-container')
    expect(abcContainterElement).toBeInTheDocument()
  })
  
  
  
  test('after 1 second, the ref is created', async () =&gt; {
    const div = document.createElement('div');
  
    const {getByText, getByTestId} = render(&lt;AbcContainer/&gt;, div)
  
    const abcContainerElement = getByTestId('abc-container')
    await wait(() =&gt; getByText(/the position of the box is 169.5,134/i))
    const words = getByText(/the position of the box is 169.5,134/i)
    expect(words).toBeInTheDocument()
  })
  </code></pre>
  
  <p>By modifying the test files I have successfully able to get the Forwarded ref to <em>appear</em> populated:</p>
  
  <p><strong>however</strong>, when I call getBoundingRectRect() </p>
  
  <p>please pull the repo and run the specs with <code>yarn test --watchAll --coverage</code></p>
  
  <p>UPDATE 2019-12-13@13-23EST:</p>
  
  <p>basically what's happening now is that I think I've isolated this down to being an issue with the setTimeout</p>
  
  <p>for some reason I cannot see, when I examine the objects immediately from within <code>componentDidMount</code> I see the refs (now both def_ref and abc_ref) correctly populated:</p>
  
  <pre><code>def_thing_ref:
       { current:
          HTMLDivElement {
  </code></pre>
  
  <p>//abc_container.js</p>
  
  <pre><code>  componentDidMount() {
      console.log("componentDidMount... this= ", this) // when examined this way, the def_thing_ref and abc_ref are coming out correctly populated
  
      if (this.def_thing_ref.current) {
        let rect = this.def_thing_ref.current.getBoundingClientRect()
        console.log("the obejct's rect is ", rect)
  
        this.setState({
          visible: true,
          message: "the position of the box is " + rect.x + "," + rect.y +
            "....I know this because the ref for this.def_thing_ref.current is populated"
        })
      } else {
        console.log(".....***** no current ref object********************")
      }
  </code></pre>
  
  <p>However, if I wrap this in a <code>setTimeout</code> the refs still seem to be refs but they are coming out as <code>{current: null}</code></p>
  
  <p>when modified to be within a <code>setTimeout</code> the code look like:</p>
  
  <p>// abc_container.js</p>
  
  <pre><code>   componentDidMount() {
      setTimeout(() =&gt; {
        console.log("TIMEOUT componentDidMount... this= ", this) 
        if (this.def_thing_ref.current) {
          let rect = this.def_thing_ref.current.getBoundingClientRect()
          console.log("the obejct's rect is ", rect)
          this.setState({
            visible: true,
            message: "the position of the box is " + rect.x + "," + rect.y +
              "....I know this because the ref for this.def_thing_ref.current is populated"
          })
        } else {
          console.log(".....***** no current ref object********************")
        }
      }, 2500)
    }
  </code></pre>
  
  <p>and here it produces:</p>
  
  <pre><code>     TIMEOUT componentDidMount... this=  AbcContainer {
   //
          def_thing_ref: { current: null },
          abc_ref: { current: null },
  
  </code></pre>
  
  <p>as you can see, the Refs objects seem to revert(?) to { current: null } when examined after the 2.5 second timeout</p>