Тестирование и макет выборки в асинхронном использовании и эффекте Redux-Saga - PullRequest
1 голос
/ 23 мая 2019

Я тестирую функциональный компонент, использующий React-Hooks и Redux-Saga. Я могу передать параметры в URL для компонента, потому что они являются компонентом страницы входа.

Мой URL-адрес, который я передаю, является 'localhost / access / параметр ', и когда этот параметр существует, мне нужно вызвать асинхронную редукционную сагу, и, если выборка в порядке, я помещаю результат в избыточный -хранить. Когда результат находится в избыточном хранилище, у меня есть useEffect, которое проверяет результат, и, если все в порядке, я помещаю ее во вход.

Я могу смоделировать результат с помощью axios, но я перехожу на использование только fetch . я издеваюсь, но когда я использую mount(component), предоставленный энзимом, я не знаю, как ждать редукса-саги, чтобы запрос и использование эффекта выполняли вашу работу. Я помещаю консольный журнал в эффект, сагу и записываю реквизиты ввода, чтобы увидеть ваше значение prop , но значение всегда пусто. Я пытался использовать setImmediate() и process.nextTick().

Ссылки, которые я использую для написания кода: 1 , 2 , 3

Я использую formik, поэтому они передают мне некоторые реквизиты.

Мой компонент

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);

Моя сага

export function* fetchDomain(action) {
  const url = yield `${mainUrl}/${action.payload}`;
  try {
    const response = yield fetch(url).then(res => res.json());
    yield put(reduxAction(response , constants.FETCH_DOMAIN_SUCCESS));
  } catch (e) {
    yield put(reduxAction(e, constants.FETCH_DOMAIN_FAILURE));
  }
}

Мой редуктор

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();
  });
});

Я ожидаю, что мой вклад имеет значение, но он нет. Я пытался использовать jest-fetch-mock , но просто не работал, и я хочу использовать нативные методы jest, без тридцати партийных библиотек.

1 Ответ

1 голос
/ 26 мая 2019

Я не могу сказать, что не так с вашим текущим кодом.Но вместо этого хочу предложить другой подход.

В настоящее время вы тестируете как часть редукса, так и часть компонента.Это противоречит стратегии модульного тестирования, когда в идеале вы должны макетировать все, кроме тестируемого модуля.

Так что я имею в виду, что если вы сосредоточитесь на тестировании самого компонента, это будет намного проще (меньше создавать тестов) и более читабельно.Для этого вам необходимо дополнительно экспортировать развернутый компонент (Login в вашем случае).Затем вы можете проверить только результат props-vs-render:

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');
});

И то же самое для других случаев.Посмотрите с этим подходом, если вы замените redux прямым вызовом fetch() или чем-то еще, или если вы реорганизуете эти данные, чтобы они приходили от родителя, а не читали из хранилища с избыточностью, вам не нужно будет переписывать тесты, удаляя ложные переходы с сокращением.Конечно, вам все равно нужно будет тестировать редукционную часть, но это также можно сделать изолированно.

PS, и await fetchDomain(...) в useEffect нет прибыли, поскольку вы не используете то, что она возвращает.await не работает как пауза, и этот код может запутать читателя.

...