React Hooks-如何测试全球供应商的变更

时间:2019-06-03 12:34:50

标签: reactjs react-hooks react-testing-library react-hooks-testing-library

我正在尝试测试以下情况:

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

为此,我有2个提供商:

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

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

当获取的资源返回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与身份验证类似,公开了一个提供程序

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

但仍然无法正常工作,并且值仍为false

有什么主意我该如何解决这个问题?

1 个答案:

答案 0 :(得分:3)

此处react-hooks-testing-library的作者。

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

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

  1. 更新为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();
  });
  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可能会一天停止运作。