我正在尝试测试以下场景:
- 拥有过期 token 的用户尝试访问未经授权的资源
- 资源返回 401 错误
- 应用程序将全局状态“isExpiredSession”更新为 true
为此,我有 2 个提供商:
- 身份验证提供程序,具有全局身份验证状态
- 负责获取资源的人
两者都有自定义 Hook ,公开这些组件的共享逻辑,即:fetchResource/expireSession
当获取的资源返回 401 状态时,它会通过共享 setState 方法在身份验证提供程序中设置 isExpiredSession 值。
AuthenticationContext.js
从 'react' 导入 React, { createContext, useState };
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 和认证类似,暴露一个Provider
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);
});
};
...
然后,在我的测试中,使用react-hooks-testing-library我执行以下操作:
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
,但是,资源所做的更改似乎并未反射(reflect)在它上。 - 通过 Resource hook 公开 isExpiredSession 变量,即:
return {
...
isExpiredSession: authState.isExpiredSession,
...
};
我预计到那时这条线会起作用:
expect(result.current.isExpiredSession).toBeTruthy();
但仍然无法正常工作,并且该值仍然为 false
知道如何解决这个问题吗?
最佳答案
这里是react-hooks-testing-library
的作者。
无法运行代码有点困难,但我认为您的问题可能是多个状态更新无法正确批处理,因为它们没有包含在act
中> 打电话。 an alpha release of react
(v16.9.0-alpha.0) 对异步调用进行操作
的能力我们有 an issue tracking it也是如此。
所以可能有2种方法可以解决:
- 更新到 alpha 版本并将
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();
});
您对使用 alpha 版本的兴趣可能会决定您选择哪个选项,但是,选项 1 更“面向 future ”。一旦 alpha 版本发布稳定版本,选项 2 可能有一天会停止工作。
关于reactjs - React Hooks - 如何测试全局提供者的更改,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56427502/