1

I currently have a react component with an onChange method.

I want to test the onChange method like if someone were to type in, or paste things in. The problem is the handleChange is dependent on the parent components using it. What's the best way to test for the simulation? Do I need to reference the parent component's handleChange method and simulate that?

CodeSandbox (i didn't add the tests here)

https://codesandbox.io/s/react-basic-example-cwdh1?from-embed

Updated Current Test:

const handleChange = jest.fn();

const initProps = {
  name: "Title",
  label: "Body",
  type: "text",
  value: "",
  handleChange
};

describe("TextBox", () => {
    let wrapper;
    beforeEach(() => {
      wrapper = mount(<TextBox {...props} />);
    });

    afterEach(() => {
      // resets the mocked fn after each test
      handleChange.mockClear();
    });

    it("should call handleChange with a new value", () => {
      const value = "testing input";
      wrapper.find("input").simulate("change", { target: { value } });

      expect(handleChange).toHaveBeenCalledWith(value);
    });
  });

Error:

Expected: "testing input"
Received: {"_dispatchInstances": null, "_dispatchListeners": null,
 "_targetInst": ......more fields... "nativeEvent": {"target": <input … />,
 "type": "change"}, "target": {"value": "testing input"}, "timeStamp":
 1576040322012, "type": "change"}

I know I'm getting this error because the value is coming out of the formfields. Is there a way I can extract the target field out?

lost9123193
  • 10,460
  • 26
  • 73
  • 113

1 Answers1

2

I think you're a little confused about your testing methods. What you're trying to do is a component/integration test with a parent and child component (manipulating a parent class field to update a child's value prop), when what you actually have is an isolated reusable TextBox child component without a parent, which requires a unit test.

So you have two options:

Option 1: Mount the parent component and manipulate its class fields to update this child component (and/or manipulate the child's event handler props which then updates the parent's state... and so on). This will test synchronicity between a parent and its child for that ONE particular component. This is especially useful for testing a component (like a form) that manages state and allocates it to some children (form elements) components.

Option 2: Shallow/Mount the child component and manipulate its event handlers to trigger jest mocked parent props (like manipulating an input's onChange prop and expecting it to call a mocked parent handleChange prop with a value). It doesn't actually update the child but simulates it. This is especially useful for testing SHARED reusable child components that can have many different parent components.

Which option to choose will vary from dev to dev and project to project. But in an ideal world, your testing suites will follow something similar to the "testing pyramid": enter image description here

In your case, I'd recommend that if this child component is shared with many parent components across your app, then do both parent integration tests and a child unit test (this will cause some overlapping tests, but ensures compatibility across the app if either a parent or the child were to change or be updated). If this component is only being reused within a single parent component, then just do a single parent integration test. That said, this just my recommendation -- not a rule!


On a separate note, your TextBox component doesn't utilize state, so instead of using a class, you can simply use a stateless function:

const TextBox = ({ value, type, handleChange }) => (
  <Form.Control
    type={type}
    value={value}
    onChange={handleChange}
  />
);

Here's an example of how you can test this component:

Edit React - Basic Example

Some notes: Your TextBox is essentially a third party component that has already, presumably, been tested by the react-bootstrap maintainers. So from a practical standpoint, creating a test for this child component is redundant. Instead, I'd recommend testing the parent component and expecting the parent's state to update when this child TextBox's input has been changed. Nevertheless, the codesandbox includes tests for both a TextBox and a Form component. In regards to why your handleChange mocked function was being called with multiple props... it's being called with a Synthetic Event, which is normal (my brain fart for not taking this into account). That said, you can assert against the Synthetic Event by using expect.objectContaining by either traversing the mockedFn.mock.calls arrays or by asserting against the mockedFn itself (both are included in the TextBox tests).

Matt Carlotta
  • 18,972
  • 4
  • 39
  • 51
  • thanks for the suggestion, would I still need to rely on a parent component for option 2? I was wondering if there was a way I could just trigger a fake function with the onHandle – lost9123193 Dec 11 '19 at 01:42
  • No to the first question. The second option would triggered a `jest.fn()` prop function. Again, it simulates a change, but doesn't actually call `setState` and change the `value`, but calls `handleChange` (the jest fn) with a `value`. And yes to the second question (see previous sentence). – Matt Carlotta Dec 11 '19 at 01:53
  • In simple terms: 1.) Pass in a jest.fn as `handleChange` (I like to define it outside of my test block for easy use). 2.) Change the `input` via its `onChange` event handler. 3.) Assert against the change: `expect(handleChange).toHaveBeenCalledWith("testing input");`. If you want to assert against an actual input value change, then you'll need to mount the parent that passes down a real `handleChange` prop. – Matt Carlotta Dec 11 '19 at 02:00
  • Updated answer with an example test (located at the bottom of the answer). – Matt Carlotta Dec 11 '19 at 03:20
  • thanks! I ended up getting a result of: Received: {"_dispatchInstances": null,......"target": {"value": "testing input"}. Is looks like the result was nested? – lost9123193 Dec 11 '19 at 04:35
  • I updated my question with the error message I'm getting. I'm using react bootstrap's form control. I think the target might be embedded in the form control – lost9123193 Dec 11 '19 at 05:04
  • Please create a codesandbox with a working example of your code so I can help you further (strip it down to bare essentials for what you're trying to test -- dont need styles/non-related components, etc.). As is, I'm not familiar with React Bootstrap. That said, it is working, although I'm not sure why it's passing along other properties as well. When you link me a working codesandbox, I'll investigate it and revise my answer. – Matt Carlotta Dec 11 '19 at 07:55
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/204074/discussion-between-lost9123193-and-matt-carlotta). – lost9123193 Dec 11 '19 at 18:17