jefflau / jest-fetch-mock

Jest mock for fetch
MIT License
883 stars 116 forks source link

Executing the same call twice with different results #100

Open KDederichs opened 5 years ago

KDederichs commented 5 years ago

Hey,

I'm trying to write a test for a network call that retries itself after it gets a response with an error code.

The whole thing looks like this:

  for (let i = 0; i < maxRetries; i++) {
    try {
        return yield apiCall;
    } catch (err) {
      if (i < maxRetries) {
        yield call(delay, retryDelay);
      } else {
        throw err;
      }
    }
  }

I set up the mock like this:

      fetch
        .once(addressUserFailure, {
          headers: { 'Content-Type': 'application/json' },
          status: 502,
        }).once(addressUserFailure, {
          headers: { 'Content-Type': 'application/json' },
          status: 200,
        });

My problem is now that mockResponseOnce as well as mockResponses will always return the first 502 response, causing the test to fail. Is this intended behavior?

jefflau commented 5 years ago

Where are you using mockResponses?

KDederichs commented 5 years ago

I tried the same I did with the example above using mockResponses to see if that changes the result. It does not. Another thing to note is that fetch.mock.calls.length is always 1 even though the network call is called multiple times

jefflau commented 5 years ago

That is definitely not expected behavior. I’ve never used it with generators though. What does your whole test look like? After you mock it, how do you call your function to test?

KDederichs commented 5 years ago

The test looks something like this:

     fetch.once('', {
        headers: { 'Content-Type': 'application/json' },
        status: 503,
      });
      fetch.once(addressDeleteSuccess, {
        headers: { 'Content-Type': 'application/json' },
        status: 200,
      });
      await sagaTester.dispatch(setAccessToken('123'));
      await sagaTester.dispatch(deleteAddress(addressData));
      await sagaTester.waitFor(ADDRESS_API_REQUEST_SUCCESS);
      expect(sagaTester.getLatestCalledAction()).toEqual(
        addressApiRequestSuccess(),
      );
      expect(fetch.mock.calls.length).toEqual(2);
      expect(sagaTester.wasCalled(FETCH_CUSTOMER)).toEqual(true);

The saga calls

    const result = yield call(
      retryWrapper,
      graphQLClient.request(customerAddressUpdate, variables),
    );

which feeds the graphQL call into the retry loop I posted above.

jefflau commented 5 years ago

I came back to look at this again. I just realised what your code is doing. Can you try doing

      fetch
        .mockReject(new Error('rejected')
        .once(addressUserFailure, {
          headers: { 'Content-Type': 'application/json' },
          status: 200,
        });

I think the problem you are having is that it will never get to the catch as you aren't throwing an error

KDederichs commented 5 years ago

That fixed half of my problem I'd say. Now the responses will correctly cycle through but it still doesn't increase the fetch.mock.calls.length constantly being 1. Unless I misunderstood what this is used for?

jefflau commented 5 years ago

It definitely should not be 1.

Can you create replicate the bug in a simplified repo?

KDederichs commented 5 years ago

Sorry, took me a bit but here is a repo with one simple test case showing that behavior: https://github.com/KDederichs/fetch_mock_test

jefflau commented 5 years ago

I've PR'd your repo with an async await example.

I'm not familiar with sagas, so I couldn't really debug yours for you. But it feels like the initial call uses the fetch mock, but the calls after don't seem to call fetch?

With async await I seem to be getting the calls in the fetch mock