React Hooks - Как протестировать изменения на глобальных провайдерах - PullRequest
10 голосов
/ 03 июня 2019

Я пытаюсь проверить следующий сценарий:

  • Пользователь с просроченным токеном пытается получить доступ к ресурсу, на который у него нет прав доступа
  • Ресурсы возвращают ошибку 401
  • Приложение обновляет глобальное состояние "isExpiredSession" до true

Для этого у меня есть 2 провайдера:

  • Поставщик аутентификации с состоянием глобальной аутентификации
  • Ответственный за получение ресурса

Существуют пользовательские хуки для обоих, которые предоставляют общую логику этих компонентов, то есть: fetchResource / expireSesssion

Когда извлеченный ресурс возвращает состояние 401, он устанавливает значение isExpiredSession в поставщике аутентификации через совместное использование метода setState.

AuthenticationContext.js импортировать React, {createContext, useState} из'act ';

const AuthenticationContext = createContext([{}, () => {}]);

const initialState = {
  userInfo: null,
  errorMessage: null,
  isExpiredSession: false,
};

const AuthenticationProvider = ({ authStateTest, children }) => {
  const [authState, setAuthState] = useState(initialState);

  return (
    <AuthenticationContext.Provider value={[authStateTest || authState, setAuthState]}>
      { children }
    </AuthenticationContext.Provider>);
};


export { AuthenticationContext, AuthenticationProvider, initialState };

useAuthentication.js

import { AuthenticationContext, initialState } from './AuthenticationContext';


const useAuthentication = () => {
  const [authState, setAuthState] = useContext(AuthenticationContext);
  ...
  const expireSession = () => {
    setAuthState({
      ...authState,
      isExpiredSession: true,
    });
  };
  ...
  return { expireSession };
 }

ResourceContext.js похож на аутентификацию, выставляя провайдера

И useResource.js имеет что-то вроде этого:

const useResource = () => {
  const [resourceState, setResourceState] = useContext(ResourceContext);
  const [authState, setAuthState] = useContext(AuthenticationContext);

  const { expireSession } = useAuthentication();

  const getResource = () => {
    const { values } = resourceState;
    const { userInfo } = authState;

    return MyService.fetchResource(userInfo.token)
      .then((result) => {
        if (result.ok) {
          result.json()
            .then((json) => {
              setResourceState({
                ...resourceState,
                values: json,
              });
            })
            .catch((error) => {
              setErrorMessage(`Error decoding response: ${error.message}`);
            });
        } else {
          const errorMessage = result.status === 401 ?
            'Your session is expired, please login again' :
            'Error retrieving earnings';
          setErrorMessage(errorMessage);
          expireSession();

        }
      })
      .catch((error) => {
        setErrorMessage(error.message);
      });
  };
  ...

Затем, на моих тестах, используя реагирующую на крючки библиотеку, я делаю следующее:

  it.only('Should fail to get resource with invalid session', async () => {
    const wrapper = ({ children }) => (
      <AuthenticationProvider authStateTest={{ userInfo: { token: 'FOOBAR' }, isExpiredSession: false }}>
        <ResourceProvider>{children}</ResourceProvider>
      </AuthenticationProvider>
    );
    const { result, waitForNextUpdate } = renderHook(() => useResource(), { wrapper });

    fetch.mockResponse(JSON.stringify({}), { status: 401 });

    act(() => result.current.getResource());
    await waitForNextUpdate();

    expect(result.current.errorMessage).toEqual('Your session is expired, please login again');
    // Here is the issue, how to test the global value of the Authentication context? the line below, of course, doesn't work
    expect(result.current.isExpiredSession).toBeTruthy();
  });

Я пробовал несколько решений:

  • Рендеринг useAuthentication также на тестах, однако изменения, сделанные Ресурсом, похоже, не отражаются на нем.
  • Предоставление переменной isExpiredSession через ловушку Resource, т. Е.
      return { 
            ...
            isExpiredSession: authState.isExpiredSession,
            ...
       };

Я ожидал, что к тому времени эта строка будет работать:

expect(result.current.isExpiredSession).toBeTruthy();

Но все еще не работает, и значение все еще ложно

Есть идеи, как реализовать решение этой проблемы?

1 Ответ

3 голосов
/ 11 июня 2019

Автор react-hooks-testing-library здесь.

Немного сложно без возможности запуска кода, но я думаю Ваша проблема может заключаться в неправильных пакетных обновлениях состояний, поскольку они не упакованы в вызов act. Возможность act для асинхронных вызовов доступна в альфа-версии react (v16.9.0-alpha.0) , и у нас есть проблема, отслеживающая также .

Так что может быть 2 способа решить:

  1. Обновление до альфа-версии и перемещение waitForNextUpdate в act обратный вызов
npm install react@16.9.0-alpha.0
  it.only('Should fail to get resource with invalid session', async () => {
    const wrapper = ({ children }) => (
      <AuthenticationProvider authStateTest={{ userInfo: { token: 'FOOBAR' }, isExpiredSession: false }}>
        <ResourceProvider>{children}</ResourceProvider>
      </AuthenticationProvider>
    );
    const { result, waitForNextUpdate } = renderHook(() => useResource(), { wrapper });

    fetch.mockResponse(JSON.stringify({}), { status: 401 });

    await act(async () => {
      result.current.getResource();
      await waitForNextUpdate();
    });

    expect(result.current.errorMessage).toEqual('Your session is expired, please login again');

    expect(result.current.isExpiredSession).toBeTruthy();
  });
  1. Добавить в секунду waitForNextUpdate вызов
  it.only('Should fail to get resource with invalid session', async () => {
    const wrapper = ({ children }) => (
      <AuthenticationProvider authStateTest={{ userInfo: { token: 'FOOBAR' }, isExpiredSession: false }}>
        <ResourceProvider>{children}</ResourceProvider>
      </AuthenticationProvider>
    );
    const { result, waitForNextUpdate } = renderHook(() => useResource(), { wrapper });

    fetch.mockResponse(JSON.stringify({}), { status: 401 });

    act(() => result.current.getResource());

    // await setErrorMessage to happen
    await waitForNextUpdate();

    // await setAuthState to happen
    await waitForNextUpdate();

    expect(result.current.errorMessage).toEqual('Your session is expired, please login again');

    expect(result.current.isExpiredSession).toBeTruthy();
  });

Ваш аппетит к использованию альфа-версий, скорее всего, будет определять, какой вариант вы выберете, но вариант 1 является более «перспективным». Вариант 2 может перестать работать однажды, когда альфа-версия выйдет в стабильном выпуске.

...