React Hooks - Как протестировать изменения на глобальных провайдерах
Я пытаюсь проверить следующий сценарий:
- Пользователь с просроченным токеном пытается получить доступ к ресурсу, на который у него нет прав доступа.
- Ресурсы возвращают ошибку 401
- Приложение обновляет глобальное состояние isExpiredSession до true
Для этого у меня есть 2 провайдера:
- Поставщик аутентификации с состоянием глобальной аутентификации
- Ответственный за выбор ресурса
Для обоих есть свои собственные зацепки, представляющие общую логику этих компонентов, а именно: fetchResource/expireSesssion
Когда извлеченный ресурс возвращает состояние 401, он устанавливает значение isExpiredSession в поставщике аутентификации посредством совместного использования метода setState.
AuthenticationContext.js
импортировать React, { createContext, useState } из 'Reaction';
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 ответ
Автор react-hooks-testing-library
Вот.
Немного сложно без возможности запуска кода, но я думаю, что ваша проблема может быть в том, что обновления нескольких состояний не пакетируются правильно, поскольку они не упакованы в act
вызов. Способность к act
на асинхронных звонках находится в альфа-версии react
(v16.9.0-alpha.0), и у нас также есть проблема с отслеживанием.
Таким образом, может быть 2 способа решить это:
- Обновите альфа-версию и переместите
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();
});
- Добавьте в секунду
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 может перестать работать через день, как только альфа-версия выйдет в стабильный выпуск.