I'm building a React Native application with TypeScript. I'm using React Native Testing Library for my component tests.
I have a simple component that renders two clickable icons and a text. It's a counter that can increment and decrement the number.
import React, { PureComponent } from "react";
import { Text, TouchableOpacity, View } from "react-native";
import { Button, Icon } from "react-native-elements";
import { getIconName } from "../../services/core";
import styles from "./styles";
export interface AmountButtonProps {
  amount: number;
  onDecrement: () => void;
  onIncrement: () => void;
  size: "small" | "large";
}
export class AmountButtons extends PureComponent<AmountButtonProps> {
  render() {
    const { amount, onDecrement, onIncrement, size } = this.props;
    const fontSize = size === "small" ? 14 : 26;
    const minusDisabled = amount <= 1;
    const plusDisabled = amount >= 25;
    return (
      <View style={styles.container}>
        <Icon
          containerStyle={[
            styles[size],
            styles.iconContainer,
            styles.minusIcon,
            minusDisabled && styles.disabled
          ]}
          onPress={onDecrement}
          type="ionicon"
          name={getIconName("remove")}
          disabled={minusDisabled}
          disabledStyle={[styles.iconDisabled, styles.disabled]}
          size={fontSize}
          component={TouchableOpacity}
        />
        <View style={[styles[size], styles.amountContainer, styles.iconContainer]}>
          <Text style={{ fontSize }}>{amount}</Text>
        </View>
        <Icon
          containerStyle={[
            styles[size],
            styles.iconContainer,
            styles.addIcon,
            plusDisabled && styles.disabled
          ]}
          onPress={onIncrement}
          type="ionicon"
          name={getIconName("add")}
          disabled={plusDisabled}
          disabledStyle={styles.iconDisabled}
          color="white"
          size={fontSize}
          component={TouchableOpacity}
        />
      </View>
    );
  }
}
export default AmountButtons;
I wanted to write a simple unit test to see if the user can see the amount. Here is what I wrote.
import React from "react";
import { debug, fireEvent, render } from "react-native-testing-library";
import { getIconName } from "../../services/core";
import AmountButtons, { AmountButtonProps } from "./AmountButtons";
const createTestProps = (props?: object): AmountButtonProps => ({
  amount: 1,
  onDecrement: jest.fn(),
  onIncrement: jest.fn(),
  size: "large",
  ...props
});
describe("AmountButtons", () => {
  const props = createTestProps();
  const { getByText, getByProps } = render(<AmountButtons {...props} />);
  it("displays the amount", () => {
    debug(<AmountButtons {...props} />);
    expect(getByText(props.amount.toString())).toBeDefined();
  });
});
The problem is this test throws the error:
● AmountButtons › displays the amount
    Component not found.
      18 |   it("displays the amount", () => {
      19 |     debug(<AmountButtons {...props} />);
    > 20 |     expect(getByText(props.amount.toString())).toBeDefined();
         |            ^
      21 |   });
      22 |
      23 |   it("calls onIncrement", () => {
      at Object.it (app/components/AmountButtons/AmountButtons.test.tsx:20:12)
Even though in the output of debug I can see the amount being rendered:
...
       }
      >
        <Text
          style={
            Object {
              "fontSize": 26,
            }
          }
        >
          1
        </Text>
      </View>
      <Themed.Icon
...
What is going on here? Why does React Testing Library not see this text? How can I test this?
You can not do that with RTL. You are not supposed to interact with the internals of your components.
Here's how you can do it: // highlight-start jest. mock("react-select", () => ({ options, value, onChange }) => { function handleChange(event) { const option = options. find( (option) => option.
The problem is that rendering your component with RTL's render method does not happen in sync with the test case. So when the it block runs you can't be sure that this line of code
const { getByText, getByProps } = render(<AmountButtons {...props} />);
has run and getByText is bound properly.
In order to solve this you can:
it block:describe("AmountButtons", () => {
  const props = createTestProps();
  it("displays the amount", () => {
    const { getByText, getByProps } = render(<AmountButtons {...props} />);
    expect(getByText(props.amount.toString())).toBeDefined();
  });
});
beforeEach/before block:describe("AmountButtons", () => {
  const props = createTestProps();
  let getByText, getByProps;
  beforeEach(() => {
    ({ getByText, getByProps }) = render(<AmountButtons {...props} />);
  })
  it("displays the amount", () => {
    expect(getByText(props.amount.toString())).toBeDefined();
  });
});
but in this case you have to keep in let variables all the getBy helpers.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With