jameslnewell / xhr-mock

Utility for mocking XMLHttpRequest.
196 stars 48 forks source link

Is there a method similar with fetchMock.flush? #102

Open SunnyWind opened 4 years ago

SunnyWind commented 4 years ago

The method description is as follows:

.flush(waitForBody) Returns a Promise that resolves once all fetches handled by fetch-mock have resolved Useful for testing code that uses fetch but doesn’t return a promise. If waitForBody is true, the promise will wait for all body parsing methods (res.json(), res.text(), etc.) to resolve too.

jameslnewell commented 4 years ago

There isn't a similar method at the moment but it sounds like a feature that could be implemented.

Could you please provide me with a code example of how you'd use it? Is this for testing a component render?

SunnyWind commented 4 years ago

I'm writing UT for React components which mostly do an HTTP request after mounted. I need to check the state of components after the request was finished. Without the flush method, I have to wait a few milliseconds to proceed with the check.

jameslnewell commented 4 years ago

Thanks for explaining it in more detail! Could you please you provide a minimal code example that demonstrates how the feature should work which we can use as a test?

SunnyWind commented 4 years ago
import React, { useState, useEffect } from 'react';
import Enzyme, { mount } from 'enzyme';
import EnzymeAdapter from 'enzyme-adapter-react-16';
import { expect } from 'chai';
import { request } from "mithril/request";
import xhrMock from 'xhr-mock';

Enzyme.configure({ adapter: new EnzymeAdapter() });

describe('The component', () => {
  it('should update its state after it got a responce from the server', async () => {

    xhrMock.get('/count', {
      status: 200,
      body: 1
    });

    const wrapper = mount(
      <TestComp />,
    );

    // This method should wait until the request is finished
    // await xhrMock.flush();

    expect(wrapper.state('count')).to.be.equal(1);
  });

});

const TestComp = () => {
  const [count, setCount] = useState<number>(0);

  useEffect(() => {
    const res = await request({
      method: 'GET',
      url: '/count'
    });
    setCount(res);
  }, []);

  return (
  <p>{count}</p>
  );
};

I'm not sure the code is runnable...

SunnyWind commented 4 years ago

BTW, Is there a hacky and easy way to meet my requirements? I tried to modify the XMLHttpRequest and could not find a way to wrap the process into a Promise object.

prantlf commented 4 years ago

An API "wait for all" in XHRMock would be nice, but it will not be as simple as in your example, if it should work in more complicated tests. Mocks may get registered among making requests. A mocked request can be executed multiple times. Maybe a scope object would help to group the mocked calls to wait for all?

As long as there is no API for this, you can use your own promises to wait just for the calls that you need. (If the mock was called multiple times, or if it was in other script as a shared utility, you could use an event emitter instead of the promise to inform the test about the finished request.)

For example:

let respond;
const responded = new Promise(resolve => (respond = resolve));

xhrMock.get('/count', (request, response) => {
  // switch context to let the component update by the promise
  setTimeout(respond);
  return response.status(200).body(1);
});

const wrapper = mount(
  <TestComp />
);

await responded;

expect(wrapper.state('count')).to.be.equal(1);

Alternatively, you could try watching for updates of the React component.

jameslnewell commented 4 years ago

@SunnyWind I don't believe there's an easy way to implement the functionality in the current version (v2.5.1) though it will be easy to implement in the next major version (v3) which is (very slowly) under development.

jameslnewell commented 4 years ago

I was thinking the .flush()* method would resolve when all incomplete requests are completed (that includes any requests that were initiated after .flush() is called while waiting for the original requests).

@prantlf What would the API for a scope object look like? Can you provide a theoretical example? Thanks for the interim work around!

*I'm keen to hear people's opinion on the method name! e.g. .wait|flush|settle() etc