Is it possible to wait for a component to render? React Testing Library/Jest

Viewed 25257

I have a component. It has a button. Upon pressing the button, I am changing the style of the button text (color) using setState function. When I am testing the changed component, the test is failing because the change happens asynchronously. I want to do something as is given here (https://testing-library.com/docs/dom-testing-library/api-async/)

const button = screen.getByRole('button', { name: 'Click Me' })
fireEvent.click(button)
await screen.findByText('Clicked once')
fireEvent.click(button)
await screen.findByText('Clicked twice')

But rather than waiting for the text to change. I want to wait for the text color to change. Thanks

This is the code for my button

<Button onPress = {() => {this.setState({state : 1});}}>
<Text style = {style}>Button Text</Text>
</Button>

So when this button is pressed. state is set to 1. And in render :

if(this.state.state === 1) style = style1
else style = style2;

But it can be seen from logs that render is called after the test checks for the styles. So How can I wait for the render to complete before checking if the font color has been changed?

Here is the testing code

test('The button text style changes after press', () => {
  const {getByText} = render(<Component/>);
  fireEvent.press(getByText('button'));
  expect(getByText('button')).toHaveStyle({
    color : '#ffffff'
  });
})
2 Answers

It looks like you have a custom button, not a native button. I'm guessing your component is something like this:

import React from "react";
import {Text, TouchableOpacity, View} from "react-native";

const Button = ({pressHandler, children}) => (
  <TouchableOpacity onPress={pressHandler}>
    {children}
  </TouchableOpacity>
);

const ColorChangingButton = ({text}) => {
  const [color, setColor] = React.useState("red");
  const toggleColor = () => setTimeout(() => 
    setColor(color === "green" ? "red" : "green"), 1000
  );
  return (
    <Button pressHandler={toggleColor}>
      <Text style={{color}}>{text}</Text>
    </Button>
  );
};
export default ColorChangingButton;

If so, you can test it with waitFor as described here:

import React from "react";
import {
  fireEvent, 
  render,
  waitFor,
} from "@testing-library/react-native";
import ColorChangingButton from "../src/components/ColorChangingButton";

it("should change the button's text color", async () => {
  const text = "foobar";
  const {debug, queryByText} = render(<ColorChangingButton text={text} />);
  fireEvent.press(queryByText(text));
  await waitFor(() => {
    expect(queryByText(text)).toHaveStyle({color: "green"});
  });
});

For a native button which has rigid semantics for changing colors and doesn't accept children, instead using title="foo", a call to debug() shows that it expands to a few nested elements. You can use

const text = within(queryByRole("button")).queryByText(/./);
expect(text).toHaveStyle({color: "green"});

inside the waitFor callback to dip into the button's text child and wait for it to have the desired color.

I used the same packages/versions for this post as shown in React Testing Library: Test if Elements have been mapped/rendered.

You can try

<Text style = {this.state.state === 1 ? style1 : style2}>Button Text</Text>

This will consequently lead to the style being defined all time. So you don't have to wait for the setState to complete.
Edit
You can use the callback provided by setState function to perform your tests for styles.

this.setState({
   state : 1
} , () => {
    //this is called only after the state is changed
    //perform your test here
})
Related