How to test implementation details with react-testing-library
Tags: react, javascript, reacttestinglibrary, enzyme
Published: 2019-03-31 03:00:31

---
title: How to test implementation details with react-testing-library
published: true
description: In the slim chance that you need to test implementation details of a react component, this is how you do it with react-testing-library
tags: react, javascript, react-testing-library, enzyme
---

If you are using enzyme to test your react components, you should consider switching to react-testing-library as soon as possible, its API is intuitive, easy to use, and it encourages writing tests in a way that your end users use your application.

With that being said, when you write tests with react-testing-library, it does not directly expose a way to test the implementation details of a component, because your users do not care if you're using a stateless functional component, a stateful functional component (a component with hooks), or a class component.  With enzyme, it's easy to test implementation details, which then encourages engineers to ... test implementation details.

I had the odd scenario where it made sense to test implementation details, but I only knew how to do so with enzyme, so I made a tweet listing my concerns, to which react-testing-library's author, Kent C. Dodds, promptly replied saying that I can test implementation details by using refs. Tweet available here: https://twitter.com/mcrowder65/status/1100587157264187392

So I set out to find out how to accomplish this!


The specific use case I was having at work was with ag-grid, so I wanted to reproduce here as well, let's render a simple grid with the following code:

```jsx
import React from "react";
import { AgGridReact } from "ag-grid-react";
import "ag-grid-community/dist/styles/ag-grid.css";
import "ag-grid-community/dist/styles/ag-theme-balham.css";
import CellEditor from "./custom-cell";

function App() {
  const columnDefs = [
    {
      headerName: "Make",
      field: "make",
      cellEditorFramework: CellEditor,
      editable: true
    },
    {
      headerName: "Model",
      field: "model",
      cellEditorFramework: CellEditor,
      editable: true
    },
    {
      headerName: "Price",
      field: "price",
      cellEditorFramework: CellEditor,
      editable: true
    }
  ];
  const rowData = [
    {
      make: "Toyota",
      model: "Celica",
      price: 35000
    },
    {
      make: "Ford",
      model: "Mondeo",
      price: 32000
    },
    {
      make: "Porsche",
      model: "Boxter",
      price: 72000
    }
  ];

  return (
    <div
      className="ag-theme-balham"
      style={{
        height: "130px",
        width: "600px"
      }}
    >
      <AgGridReact columnDefs={columnDefs} rowData={rowData} />
    </div>
  );
}

export default App;

```

This produces the following:
<img src="https://thepracticaldev.s3.amazonaws.com/i/vmsn6xgq0kpsauc58rxe.png"/>


If you look at `columnDefs`, you'll notice that I added `cellEditorFramework`, this allows me to add my own, custom cell editor here. Let's look at that custom cell editor.

```jsx
import React from "react";
import { TextField } from "@material-ui/core";

class CellEditor extends React.Component {
  state = {
    value: this.props.value
  };
  getValue() {
    return this.state.value;
  }

  handleChange = event => {
    this.setState({ value: event.target.value });
  };

  render() {
    return <TextField value={this.state.value} onChange={this.handleChange} />;
  }
}

export default CellEditor;

```

You'll notice here, that we are just setting local state values which takes the initial prop value and syncs to local state.  But one thing you'll notice here if you look closely, `getValue` is completely unnecessary, it does not provide any value! Let's look at what ag-grid does now when I start editing with `getValue` removed:

<img src="https://thepracticaldev.s3.amazonaws.com/i/xrpcutqbn6njoutupg5i.gif"/>

The value disappears once we're done editing! This is because ag-grid calls getValue to get the final value once we're done editing, it doesn't know that the value is stored in state. So, there are three things one must do to ensure that this code works.

1. Add getValue back.
2. Add a jsdoc like so:
    ```js
   /**
   * Ag-grid calls this function to get the final value once everything is updated.
   * DO NOT DELETE
   * @returns {String|Number} this.state.value
   */
  getValue() {
    return this.state.value;
  }
```

3. Create a unit test that tests that `getValue()` returns `this.state.value`
Let's write that unit test!

If you read the tweet, you noticed that Kent said, "You can do that with with react-testing-library using a ref in what you render in your test.", then let's do that.

In custom-cell.test.js:

```jsx
import React from "react";
import { render } from "react-testing-library";
import CustomCell from "../custom-cell";

test("that getData returns this.state.data", () => {
  const ref = React.createRef();
  render(<CustomCell ref={ref} />);
  expect(ref.current.getValue()).toEqual(ref.current.state.value);
});
```

Now we know, if someone gets rid of `getValue` for some reason, it will fail, and you are protected.

Again, there are VERY rare cases where you need to do this, so please think twice, maybe even thrice, whether or not you should be doing this.

Source code available here: https://github.com/mcrowder65/rtl-testing-implementation-details