Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I manually trigger a state change when testing using react-testing-library?

I'm still in the process of moving my Enzyme tests over to react-testing-library, and I have a fairly common scenario where, when a component mounts, it kicks off an Ajax request to get some data. Just before the fetch starts, it sets some state value to indicate it is loading, which in turn renders a spinner. When it's finished, the state is updated with both the data, and "loadingState" is set to "Completed" or "Failed", where appropriate.

import React, { useEffect, useState } from "react";
import { SwapSpinner } from "react-spinners-kit";
import styled from "styled-components";
import * as R from "ramda";

import { getPeople } from "./getPeople";

const FlexCenter = styled.div`
  height: 250px;
  display: flex;
  justify-content: center;
  align-items: center;
`;

const loadingStates = {
  notStarted: "notStarted",
  isLoading: "isLoading",
  success: "success",
  failure: "failure"
};

function App() {
  const [people, setPeople] = useState([]);
  const [isLoading, setLoading] = useState(loadingStates.notStarted);

  useEffect(() => {
    setLoading(loadingStates.isLoading);
    getPeople()
      .then(({ results }) => {
        setPeople(results);
        setLoading(loadingStates.success);
      })
      .catch(error => {
        setLoading(loadingStates.failure);
      });
  }, []);

  return (
    <div>
      {R.cond([
        [
          R.equals(loadingStates.isLoading),
          () => (
            <FlexCenter data-testid="spinner">
              <SwapSpinner />
            </FlexCenter>
          )
        ],
        [
          R.equals(loadingStates.success),
          () => (
            <ul data-testid="people-list">
              {people.map(({ name }) => (
                <li key={name}>{name}</li>
              ))}
            </ul>
          )
        ],
        [R.equals(loadingStates.failure), <div>An error occured</div>]
      ])(isLoading)}
    </div>
  );
}

export default App;

With Enzyme, I could manually set the state to any one of the loadingStates keys, and assert that the render conditional renders the appropriate changes.

Is there a way that I can do this in RTL?

like image 747
jktravis Avatar asked Feb 23 '19 02:02

jktravis


People also ask

How do I change the state value in React test library?

You can click the button which will set the val to true. it('small container visible only when val is true', () => { const {queryByText, getByTestId} = render(<MyComponent />); const clickButton = getByTestId('your.button.test.id'); fireEvent.

Which function is used to firing event in React testing library?

createEvent[eventName] ​


1 Answers

You can not do that with RTL. You are not supposed to interact with the internals of your components.

This is roughly how I would test your component:

import { getPeople } from "./getPeople";
jest.mock('./getPeople')

test('skeleton of a test', async () => {
  const people = [/* Put some mock people in here */]
  getPeople.mockResolvedValueOnce({ results: people })
  render(<App />)

  expect(/* Somehow get the loading spinner */).toBeInTheDocument()

  await wait(() => expect(/* Here you check that the people is on the page */).toBeInTheDocument())

  // We also check that the API gets called
  expect(getPeople).toHaveBeenCalledOnce()
  expect(getPeople).toHaveBeenCalledWith()
})

As you can see, I'm not checking what's the internal state of App. Instead, I'm checking that a loading spinner is shown and that after that the people appear on the screen and that the API gets called.

This test is more reliable because you're testing what a user would see and not the implementation details.

like image 184
Giorgio Polvara - Gpx Avatar answered Oct 17 '22 21:10

Giorgio Polvara - Gpx