Adam Thompson
Adam Thompson

Reputation: 3506

Wait for fetch() to resolve using Jest for React test?

In my componentDidMount of a React.Component instance I have a fetch() call that on response calls setState.

I can mock out the request and respond using sinon but I don't know when fetch will have resolved it's promise chain.

componentDidMount() {
  fetch(new Request('/blah'))
    .then((response) => {
      setState(() => {
        return newState;
      };
    });
}

In my test using jest with enzyme:

it('has new behaviour from result of set state', () => {
  let component = mount(<Component />);

  requests.pop().respond(200);

  component.update() // fetch() has not responded yet and
                     // thus setState has not been called yet
                     // so does nothing

  assertNewBehaviour(); // fails

  // now setState occurs after fetch() responds sometime after
});

Do I need to flush the Promise queue/callback queue or something similar? I could do a repeated check for newBehaviour with a timeout but that's less than ideal.

Upvotes: 3

Views: 3555

Answers (4)

Nagibaba
Nagibaba

Reputation: 5358

Mock it using nock (link) and you can use almost immediately. Also there is a video tutorial (link). Don't forget to use waitFor.

test.only('should use API response', async () => {
  nock(BASE_URL)
    .get(`/account/${ACCOUNT_ID}/lists?page=1`)
    .reply(200, listsMockResponse1page);
 const {getByTestId} = render(component);
...

Upvotes: 0

Nicola Pedretti
Nicola Pedretti

Reputation: 5166

The react testing library has a waitFor function that works perfectly for this case scenario.

I will give an example with hooks and function as that is the current react pattern. Lets say you have a component similar to this one:

export function TestingComponent(props: Props) {
    const [banners, setBanners] = useState<MyType>([]);

    React.useEffect(() => {
        const response = await get("/...");
        setBanners(response.banners);
    }, []);

    return (
        {banners.length > 0 ? <Component> : </NoComponent>}
    );
}


Now you can write a test like this to make sure that when banners are set Component is rendered

test("when the banner matches the url it renders", async () => {
        const {container} = render(<TestingComponent />);
        await waitFor(() => {expect(...).toBe(...)});
    });

waitFor will wait for the condition in the function to be met before proceeding. There is a timeout that will fail the test if the condition is not met in X time. Check out the react testing library docs for more info

Upvotes: 0

Jemi Salo
Jemi Salo

Reputation: 3751

Since you're not making any real api calls or other time-consuming operations, the asynchronous operation will resolve in a predictably short time.

You can therefore simply wait a while.

it('has new behaviour from result of set state', (done) => {
  let component = mount(<Component />);
  requests.pop().respond(200);

  setTimeout(() => {
    try {
      component.update();
      assertNewBehaviour();
      done();
    } catch (error) {
      done(error);
    }
  }, 1000);
});

Upvotes: 0

Adam Thompson
Adam Thompson

Reputation: 3506

The best answer it seems is to be use a container pattern and pass down the API data from a container class with separated concerns and test the components separately. This allows the component under test to simply take the API data as props and makes it much more testable.

Upvotes: 1

Related Questions