Skip to content

Methods of component are unavailable from render #492

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
adueck opened this issue Sep 25, 2019 · 2 comments
Closed

Methods of component are unavailable from render #492

adueck opened this issue Sep 25, 2019 · 2 comments

Comments

@adueck
Copy link

adueck commented Sep 25, 2019

  • react-testing-library version: 9.1.4
  • react version: 16.9.0
  • node version: 12.7.0
  • npm (or yarn) version: 6.11.3

Relevant code or config:

Let's say I'm trying to test a component like this:

// tester.tsx

import React, { Component } from "react";

class Tester extends Component<any, any> {
    constructor(props: any) {
        super(props);
        this.state = {
            message: "Hi",
        };
        this.changeMessage = this.changeMessage.bind(this);
    }

    public changeMessage(message: string) {
        this.setState({ message });
    }

    public render() {
        return <div className="message">{this.state.message}</div>;
    }
}

export default Tester;

With a test like this:

// tester.test.tsx

import React from "react";
import { render, unmountComponentAtNode } from "react-dom";
import Tester from "./tester";

let container: any = null;
beforeEach(() => {
  container = document.createElement("div");
  document.body.appendChild(container);
});

afterEach(() => {
  unmountComponentAtNode(container);
  container.remove();
  container = null;
});

it("the method should change the message", () => {
  // @ts-ignore
  const { changeMessage } = render(<Tester />, container);
  changeMessage("new message");
  const message = document.querySelector(".message");
  expect(message.innerHTML).toBe("new message");
});

This works just fine when using the regular render function from react-dom.

But when I need to use the render function from @testing-library/react (because I need some extra features like the wrapper option etc.) I can no longer access the methods from my class component. The following code doesn't work as expected, because I can't get the method like I was able to with the regular render function from react-dom.

// tester.test.tsx

import React from "react";
import { unmountComponentAtNode } from "react-dom";
import { render } from '@testing-library/react';
import Tester from "./tester";

let container: any = null;
beforeEach(() => {
  container = document.createElement("div");
  document.body.appendChild(container);
});

afterEach(() => {
  unmountComponentAtNode(container);
  container.remove();
  container = null;
});

it("the method should change the message", () => {
  // @ts-ignore
  const { changeMessage } = render(<Tester />, { container });
  changeMessage("new message");
  const message = document.querySelector(".message");
  expect(message.innerHTML).toBe("new message");
});

This doesn't work because changeMessage is now undefined.

Problem description:

There are many cases where we need to access the methods of components for unit testing. If this is supposed to be seen as a replacement for the render of react-dom (is it? 🤔), then it would be expected behavior to be able to access methods from components. This would allow for cases like this where we need to get a method from a child component, for instance:

it("the method should change the message", () => {
  // @ts-ignore
  const { changeMessage } = render(<Tester />, { 
    container,
    wrapper: Router, 
  });
  changeMessage("new message");
  const message = document.querySelector(".message");
  expect(message.innerHTML).toBe("new message");
});

Suggested solution:

Make the methods accessible as they are with render from react-dom

@eps1lon
Copy link
Member

eps1lon commented Sep 25, 2019

The point of @testing-library/ is to not test implementation details. If this component is used at an application level rethink how this state change is triggered in the UI and replay these interactions in your test.

If this is a test at a library level imperative methods might be part of your public API. In this case you can (just like your library consumer) attach a ref:

it("the method should change the message", () => {
+ const methodRef = React.createRef();
  // @ts-ignore
- const { changeMessage } = render(<Tester />, { 
+ render(<Tester ref={methodRef} />, { 
    container,
    wrapper: Router, 
  });
- changeMessage("new message");
+ methodRef.current.changeMessage("new message");
  const message = document.querySelector(".message");
  expect(message.innerHTML).toBe("new message");
});

In any case, I don't see the need for exposing imperative handles from @testing-library/react since it's impossible to detect safely if a component can hold a ref or not.

@adueck
Copy link
Author

adueck commented Sep 25, 2019

Ok, I can see now how this is against the principles, that @testing-library/react is guided by. Instead of trying to get and test the individual internal methods (which would be private/inaccessible in other cases anyhow) I am going to try to test things as they are used in the app by mocking other functions etc, kind of like this. Sorry that this was maybe inappropriate to file as a bug report. Seems that this was not expected behavior anyways.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants