10

我正在尝试测试以下场景:

  • 具有过期令牌的用户尝试访问他未授权的资源
  • 资源返回 401 错误
  • 应用程序将全局状态“isExpiredSession”更新为 true

为此,我有 2 个提供者:

  • 身份验证提供者,具有全局身份验证状态
  • 负责获取资源的人

两者都有自定义钩子,公开这些组件的共享逻辑,即:fetchResource/expireSesssion

当获取的资源返回 401 状态时,它通过共享 setState 方法在身份验证提供程序中设置 isExpiredSession 值。

AuthenticationContext.js 从'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,资源所做的更改似乎并没有反映在它上面。
  • 通过 Resource 挂钩暴露 isExpiredSession 变量,即:
      return { 
            ...
            isExpiredSession: authState.isExpiredSession,
            ...
       };

我期待到那时这条线会起作用:

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

但仍然不工作,价值仍然是假的

知道如何解决这个问题吗?

4

1 回答 1

3

这里的作者react-hooks-testing-library

无法运行代码有点困难,但我认为您的问题可能是多个状态更新没有正确批处理,因为它们没有包含在act调用中。异步调用的功能act(v16.9.0-alpha.0)的 alpha 版本中react,我们也有跟踪它的问题

所以可能有两种方法可以解决:

  1. 更新到 alpha 版本并移动waitForNextUpdateact回调中
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();
  });

您对使用 alpha 版本的兴趣可能会决定您选择哪个选项,但是,选项 1 更“面向未来”。一旦 alpha 版本达到稳定版本,选项 2 可能会在某一天停止工作。

于 2019-06-11T13:39:27.060 回答