如何在组件中测试反应useContext useReducer调度

时间:2020-07-28 20:32:23

标签: reactjs react-hooks use-effect dispatch use-context

希望有人可以为此指出正确的方向。基本上,我已经创建了一个使用钩子的react应用,特别是useContext,useEffect和useReducer。我的问题是我似乎无法获得测试以检测相关组件的点击或发送事件。

我的应用程序的精简版本可以在以下网址找到:https://github.com/atlantisstorm/hooks-testing 测试与layout.test.js脚本有关。

我尝试了各种方法,模拟分发的不同方法,useContext等,但是对此并不满意。最新版本。

layout.test.js

import React from 'react';
import { render, fireEvent } from "@testing-library/react";
import Layout from './layout';
import App from './app';
import { Provider, initialState } from './context';

const dispatch = jest.fn();

const spy = jest
  .spyOn(React, 'useContext')
  .mockImplementation(() => ({
    state: initialState,
    dispatch: dispatch
}));

describe('Layout component', () => {
  it('starts with a count of 0', () => {
    const { getByTestId } = render(
      <App>
        <Provider>
          <Layout />
        </Provider>
      </App>
    );

    expect(dispatch).toHaveBeenCalledTimes(1);

    const refreshButton = getByTestId('fetch-button');

    fireEvent.click(refreshButton);

    expect(dispatch).toHaveBeenCalledTimes(3);
  });
});

layout.jsx

import React, { useContext, useEffect } from 'react';
import { Context } from "./context";

const Layout = () => {
  const { state, dispatch } = useContext(Context);
  const { displayThings, things } = state;

  const onClickDisplay = (event) => {
    // eslint-disable-next-line
    event.preventDefault;
    dispatch({ type: "DISPLAY_THINGS" });
  };

  useEffect(() => {
    dispatch({ type: "FETCH_THINGS" });
  }, [displayThings]);

  const btnText = displayThings ? "hide things" : "display things";
  return (
    <div>
        <button data-testid="fetch-button" onClick={onClickDisplay}>{btnText}</button>
        { displayThings ? 
            <p>We got some things!</p>
          :
            <p>No things to show!</p>
        }
        { displayThings && things.map((thing) =>
            <p>{ thing }</p>
        )}
    </div>
  )
}

export default Layout;

app.jsx

import React from 'react';
import Provider from "./context";
import Layout from './layout';
const App = () => {
  return (
    <Provider>
      <Layout />
    </Provider>
  )
}

export default App;

context.jsx

import React, { createContext, useReducer } from "react";
import { reducer } from "./reducer";

export const Context = createContext();

export const initialState = {
  displayThings: false,
  things: []
};

export const Provider = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <Context.Provider value={{ state, dispatch }}>
      {children}
    </Context.Provider>
  );
};

export default Provider;

reducer.jsx

export const reducer = (state, action) => {
  switch (action.type) {
    case "DISPLAY_THINGS": {
      const displayThings = state.displayThings ? false : true; 
      return { ...state, displayThings };
    }

    case "FETCH_THINGS": {
      const things = state.displayThings ? [
          "thing one",
          "thing two"            
      ] : [];
      return { ...state, things };
    }

    default: {
      return state;
    }
  }
};

我敢肯定,当我看到答案时,答案会很简单,但是只是想弄清楚我可以检测到点击事件,也可以检测到“派遣”事件? (我已经在主应用程序中进行了单独的测试,可以正确测试调度响应/操作)

谢谢。

编辑 好的,我认为我有一个合理的解决方案,尽管不是完美的。首先,我只是向context.jsx模块添加了可选的testDispatch和testState属性。

新context.jsx

import React, { createContext, useReducer } from "react";
import { reducer } from "./reducer";

export const Context = createContext();

export const initialState = {
  displayThings: false,
  things: []
};

export const Provider = ({ children, testDispatch, testState }) => {
  const [iState, iDispatch] = useReducer(reducer, initialState);

  const dispatch = testDispatch ? testDispatch : iDispatch;
  const state = testState ? testState : iState;
  return (
    <Context.Provider value={{ state, dispatch }}>
      {children}
    </Context.Provider>
  );
};

export default Provider;

然后在layout.test.jsx中,我只是简单地传入了嘲笑的玩笑派遣函数以及必要的状态。还删除了App的外部包装,以防止道具通过。

新layout.test.jsx

import React from 'react';
import { render, fireEvent } from "@testing-library/react";
import Layout from './layout';
import { Provider } from './context';

describe('Layout component', () => {
  it('starts with a count of 0', () => {
    const dispatch = jest.fn();
    const state = {
      displayThings: false,
      things: []
    };
    const { getByTestId } = render(
      <Provider testDispatch={dispatch} testState={state}>
        <Layout />
      </Provider>
    );

    expect(dispatch).toHaveBeenCalledTimes(1);
    expect(dispatch).toHaveBeenNthCalledWith(1, { type: "FETCH_THINGS" });

    const refreshButton = getByTestId('fetch-things-button');
    fireEvent.click(refreshButton);

    expect(dispatch).toHaveBeenCalledTimes(2);
    // Important: The order of the calls should be this, but dispatch is reporting them 
    // the opposite way around in the this test, i.e. FETCH_THINGS, then DISPLAY_THINGS... 
    //expect(dispatch).toHaveBeenNthCalledWith(1, { type: "DISPLAY_THINGS" });
    //expect(dispatch).toHaveBeenNthCalledWith(2, { type: "FETCH_THINGS" });
   
    // ... so as dispatch behaves correctly outside of testing for the moment I'm just settling for
    // knowing that dispatch was at least called twice with the correct parameters.
    expect(dispatch).toHaveBeenCalledWith({ type: "DISPLAY_THINGS" });
    expect(dispatch).toHaveBeenCalledWith({ type: "FETCH_THINGS" });

  });
});

但是,有一点需要注意,如上所述,当“ fetch-things-buttons”被触发时,它以错误的顺序报告了调度。 :/因此,我只是想知道触发了正确的呼叫,但是如果有人知道为什么呼叫顺序不符合预期,我将很高兴知道。

https://github.com/atlantisstorm/hooks-testing更新以反映以上内容,如果有人有兴趣的话。

0 个答案:

没有答案