Jestjs 在异步useffect和异步Redux Saga中测试和模拟fetch

Jestjs 在异步useffect和异步Redux Saga中测试和模拟fetch,jestjs,fetch,enzyme,redux-saga,react-hooks,Jestjs,Fetch,Enzyme,Redux Saga,React Hooks,我正在测试一个功能组件,它使用React钩子和Redux传奇。我可以在URL中为组件传递参数,因为它们是登录页面组件 我传递的URL是“localhost/access/parameter”,当这个参数存在时,我需要调用一个异步redux saga,如果提取正常,我将结果放入redux存储。当结果在redux store上时,我有一个useEffect来验证结果,如果结果是正确的,我将她放入一个输入中 我可以用axios模拟结果,但我正在迁移到只使用获取。我嘲笑取回,但当我使用 mount(co

我正在测试一个功能组件,它使用React钩子和Redux传奇。我可以在URL中为组件传递参数,因为它们是登录页面组件

我传递的URL是“localhost/access/parameter”,当这个参数存在时,我需要调用一个异步redux saga,如果提取正常,我将结果放入redux存储。当结果在redux store上时,我有一个useEffect来验证结果,如果结果是正确的,我将她放入一个输入中

我可以用axios模拟结果,但我正在迁移到只使用获取。我嘲笑取回,但当我使用
mount(component)
,由enzyme提供,我不知道如何等待redux传奇,确定请求并使用effect完成您的工作。我在特效、传奇和输入道具中放了一个控制台日志,以查看您的值道具,但值始终为空。我尝试使用
setImmediate()
process.nextTick()

我用来写代码的链接:

我用的是福米克,所以他们给了我一些道具

我的组件

const Login = ({
  setFieldError, errors, response, fetchDomain, location, values, handleChange, handleBlur, setFieldValue, history,
}) => {

   useEffect(() => {
    async function fetchUrlDomain() {
      const { pathname } = location;
      const [, , domain] = pathname.split('/');

      if (typeof domain !== 'undefined') {
        await fetchDomain(domain);
      }
    }

    fetchUrlDomain();
  }, [fetchDomain, location]);

   useEffect(() => {
    if (typeof response === 'string') {
      setFieldError('domain', 'Domain not found');
      inputDomain.current.focus();
    } else if (Object.keys(response).length > 0) {
      setFieldValue('domain', response.Domain);
      setFieldError('domain', '');
    }
  }, [response, setFieldValue, setFieldError]);

return (
  <input name="domain" id="domain" value={values.domain} onChange={handleChange} onBlur={handleBlur} type="text" />
);
}

const LoginFormik = withFormik({
  mapPropsToValues: () => ({ domain: '' }),
  enableReinitialize: false,
  validateOnBlur: false,
  validateOnChange: false,
})(Login);

const mapStateToProps = () => ({ });

const mapDispatchToProps = dispatch => ({
  fetchDomain: (value) => {
    dispatch(action({}, constants.RESET_RESPONSE_DOMAIN));
    dispatch(action(value, constants.FETCH_DOMAIN_REQUEST));
  },
});

export default connect(mapStateToProps, mapDispatchToProps)(LoginFormik);
我的减速机

case constants.FETCH_DOMAIN_FAILURE:
  return { ...initialState, response: 'Domain not found' };
case constants.FETCH_DOMAIN_SUCCESS: {
  const { payload } = action;
  return {
    ...initialState,
    id: payload.Id,
    apis: payload.Apis,
    response: payload,
  };
}
case constants.RESET_RESPONSE_DOMAIN:
  return { ...initialState };
我的测试

it('input with fetch only', (done) => {
  const mockSuccessResponse = {
    Id: 'fafafafa',
    Apis: [],
    Domain: 'NAME',
  };
  const mockJsonPromise = Promise.resolve(mockSuccessResponse);
  const mockFetchPromise = Promise.resolve({
    json: () => mockJsonPromise,
  });

  global.fetch = jest.fn().mockImplementation(() => mockFetchPromise);

  const wrapper = mount(
    <Provider store={store}>
      <LoginForm
        history={{ push: jest.fn() }}
        location={{ pathname: 'localhost/login/Domain' }}
      />
    </Provider>,
  );

  process.nextTick(() => {
    const input = wrapper.find('#domain');
    console.log(input.props());
    expect(input.props().value.toLowerCase()).toBe('name');

    global.fetch.mockClear();
    done();
  });
});
it('input with fetch only',(done)=>{
常量mockSuccessResponse={
Id:‘fafafa’,
API:[],
域名:'NAME',
};
const mockJsonPromise=Promise.resolve(mockSuccessResponse);
const mockFetchPromise=Promise.resolve({
json:()=>mockJsonPromise,
});
global.fetch=jest.fn().mockImplementation(()=>mockFetchPromise);
常量包装器=装入(
,
);
process.nextTick(()=>{
const input=wrapper.find('#domain');
console.log(input.props());
expect(input.props().value.toLowerCase()).toBe('name');
global.fetch.mockClear();
完成();
});
});

我希望我的意见有价值,但他没有。我尝试过使用,但就是不起作用,我想使用本机的jest方法,没有三十方库。

我说不出您当前的代码有什么问题。但我想提出不同的方法

目前,您正在测试redux部件和组件的部件。它与单元测试策略相矛盾,理想情况下,您应该模拟除测试下的模块之外的所有内容

所以我的意思是,如果您专注于测试组件本身,它将更容易(创建的模拟更少)和更可读。为此,您需要另外导出展开的组件(
Login
)。然后,您只能测试其道具与渲染结果:

it('calls fetchDomain() with domain part of location', () => {
    const fetchDomain = jest.fn();
    const location = { pathName: 'example.com/path/sub' }
    shallow(<Login fetchDomain={fetchDomain} location={location} />);
    expect(fetchDomain).toHaveBeenCalledTimes(1);
    expect(fetchDomain).toHaveBeenCalledWith('example.com');
});

it('re-calls fetchDomain() on each change of location prop', () => {
    const fetchDomain = jest.fn();
    const location = { pathName: 'example.com/path/sub' }
    const wrapper = shallow(<Login fetchDomain={fetchDomain} location={location} />);
    fetchDomain.mockClear();
    wrapper.setProps({ location: { pathName: 'another.org/path' } });
    expect(fetchDomain).toHaveBeenCalledTimes(1);
    expect(fetchDomain).toHaveBeenCalledWith('another.org');
});
it('使用位置的域部分调用fetchDomain(),()=>{
const fetchDomain=jest.fn();
常量位置={pathName:'example.com/path/sub'}
浅();
expect(fetchDomain).toHaveBeenCalledTimes(1);
expect(fetchDomain).toHaveBeenCalledWith('example.com');
});
它('在每次更改位置属性时重新调用fetchDomain(),()=>{
const fetchDomain=jest.fn();
常量位置={pathName:'example.com/path/sub'}
常量包装器=浅();
fetchDomain.mockClear();
setProps({location:{pathName:'other.org/path'}});
expect(fetchDomain).toHaveBeenCalledTimes(1);
expect(fetchDomain).toHaveBeenCalledWith('other.org');
});
其他情况也一样。请参阅此方法,如果您使用直接调用
fetch()
或其他方法替换
redux
,或者如果您重构来自父级的数据,而不是从redux存储读取数据,则无需重写测试,将mock删除到redux。当然,您仍然需要测试redux部分,但它也可以单独完成


PS,在
useffect
中等待fetchDomain(…)是没有好处的,因为您不使用它返回的内容
Wait
不像暂停那样工作,这段代码可能会让读者感到困惑。

早上好,skyboyer。谢谢你的帮助和回答。当我向前端编写测试时,在第一步中,我测试组件/redux/sagas的功能,然后,我将尝试进行一个完整的测试,只模拟API的调用,就像集成测试一样。这种做法是错误的吗?关于你的答案,我将在这里尝试并发布结果,非常感谢!!我不能说这是错误的方式。但是组件+redux+saga的测试在我看来似乎接近验收测试。使用不同的工具(Selenium或puppeter)更容易实现。嘿,skyboyer,谢谢,你的例子帮助了我,但不能解决我的问题。Formik给了我一个改变输入的函数,如果我在没有Formik包装器的情况下使组件变浅,输入不会改变值,哈哈哈。不确定这是什么原因。请参阅,您同时提供了
onChange
。另外,也没有使用
ref
,所以我不知道Formik会如何影响这一点。您是否使用如中所述的任何中间变量?
it('calls fetchDomain() with domain part of location', () => {
    const fetchDomain = jest.fn();
    const location = { pathName: 'example.com/path/sub' }
    shallow(<Login fetchDomain={fetchDomain} location={location} />);
    expect(fetchDomain).toHaveBeenCalledTimes(1);
    expect(fetchDomain).toHaveBeenCalledWith('example.com');
});

it('re-calls fetchDomain() on each change of location prop', () => {
    const fetchDomain = jest.fn();
    const location = { pathName: 'example.com/path/sub' }
    const wrapper = shallow(<Login fetchDomain={fetchDomain} location={location} />);
    fetchDomain.mockClear();
    wrapper.setProps({ location: { pathName: 'another.org/path' } });
    expect(fetchDomain).toHaveBeenCalledTimes(1);
    expect(fetchDomain).toHaveBeenCalledWith('another.org');
});