模拟useDispatch,并在功能组件中使用该调度动作来测试参数

时间:2019-11-24 13:19:42

标签: reactjs jestjs react-hooks enzyme

您好,我正在使用笑话和酶编写功能组件的测试。当我模拟单击时,组件的params(使用useState的组件状态)会发生变化。当状态更改时,则使用useEffect调用,在useEffect中,更改后,我将使用参数调度一些异步操作。所以我想用派遣行动来测试参数。为此,我想模拟调度。我该如何实现? 任何人都可以帮助我,在此先感谢。我在下面共享代码。

component.js

import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { useSelector, useDispatch } from 'react-redux';
import { useTranslation } from 'react-i18next';
import { clientOperations, clientSelectors } from '../../store/clients';
import Breadcrumb from '../../components/UI/Breadcrumb/Breadcrumb.component';
import DataTable from '../../components/UI/DataTable/DataTable.component';
import Toolbar from './Toolbar/Toolbar.component';

const initialState = {
  search: '',
  type: '',
  pageNo: 0,
  rowsPerPage: 10,
  order: 'desc',
  orderBy: '',
  paginated: true,
};

const Clients = ({ history }) => {
  const { t } = useTranslation();
  const dispatch = useDispatch();
  const totalElements = useSelector(state => state.clients.list.totalElements);
  const records = useSelector(clientSelectors.getCompaniesData);
  const [params, setParams] = useState(initialState);

  useEffect(() => {
    dispatch(clientOperations.fetchList(params));
  }, [dispatch, params]);

  function updateParams(newParams) {
    setParams(state => ({
      ...state,
      ...newParams,
    }));
  }

  function searchHandler(value) {
    updateParams({
      search: value,
      pageNo: 0,
    });
  }

  function typeHandler(event) {
    updateParams({
      type: event.target.value,
      pageNo: 0,
    });
  }

  function reloadData() {
    setParams(initialState);
  }

  const columns = {
    id: t('CLIENTS_HEADING_ID'),
    name: t('CLIENTS_HEADING_NAME'),
    abbrev: t('CLIENTS_HEADING_ABBREV'),
  };

  return (
    <>
      <Breadcrumb items={[{ title: 'BREADCRUMB_CLIENTS' }]}>
        <Toolbar
          search={params.search}
          setSearch={searchHandler}
          type={params.type}
          setType={typeHandler}
          reloadData={reloadData}
        />
      </Breadcrumb>
      <DataTable
        rows={records}
        columns={columns}
        showActionBtns={true}
        deletable={false}
        editHandler={id => history.push(`/clients/${id}`)}
        totalElements={totalElements}
        params={params}
        setParams={setParams}
      />
    </>
  );
};

Component.test.js

const initialState = {
  clients: {
    list: {
      records: companies,
      totalElements: 5,
    },
  },
  fields: {
    companyTypes: ['All Companies', 'Active Companies', 'Disabled Companies'],
  },
};

const middlewares = [thunk];
const mockStoreConfigure = configureMockStore(middlewares);
const store = mockStoreConfigure({ ...initialState });

const originalDispatch = store.dispatch;
store.dispatch = jest.fn(originalDispatch)

// configuring the enzyme we can also configure using Enjym.configure
configure({ adapter: new Adapter() });

describe('Clients ', () => {
  let wrapper;

  const columns = {
    id: i18n.t('CLIENTS_HEADING_ID'),
    name: i18n.t('CLIENTS_HEADING_NAME'),
    abbrev: i18n.t('CLIENTS_HEADING_ABBREV'),
  };

  beforeEach(() => {
    const historyMock = { push: jest.fn() };
    wrapper = mount(
      <Provider store={store}>
        <Router>
          <Clients history={historyMock} />
        </Router>
      </Provider>
    );
  });

 it('on changing the setSearch of toolbar should call the searchHandler', () => {
    const toolbarNode = wrapper.find('Toolbar');
    expect(toolbarNode.prop('search')).toEqual('')
    act(() => {
      toolbarNode.props().setSearch('Hello test');
    });
    toolbarNode.simulate('change');
****here I want to test dispatch function in useEffect calls with correct params"**
    wrapper.update();
    const toolbarNodeUpdated = wrapper.find('Toolbar');
    expect(toolbarNodeUpdated.prop('search')).toEqual('Hello test')



  })

});


4 个答案:

答案 0 :(得分:6)

import * as redux from "react-redux";
describe('dispatch mock', function(){    
    it('should mock dispatch', function(){
            //arrange
            const useDispatchSpy = jest.spyOn(redux, 'useDispatch'); 
            const mockDispatchFn = jest.fn()
            useDispatchSpy.mockReturnValue(mockDispatchFn);

            //action
            triggerYourFlow();

            //assert
            expect(mockDispatchFn).toHaveBeenCalledWith(expectedAction);

            //teardown
            useDispatchSpy.mockClear();
    })
}});

我们从功能组件中像上面那样模拟分发,以阻止其执行真正的实现。希望对您有帮助!

答案 1 :(得分:1)

如果模拟react-redux,则可以验证useDispatch调用的参数。同样在这种情况下,您将需要重新创建useSelector的逻辑(这很简单,实际上您不必使模拟成为一个钩子)。同样,使用这种方法,您根本不需要模拟存储或<Provider>

import { useSelector, useDispatch } from 'react-redux'; 

const mockDispatch = jest.fn();
jest.mock('react-redux', () => ({
  useSelector: jest.fn(),
  useDispatch: () => mockDispatch
}));

it('loads data on init', () => {
  const mockedDispatch = jest.fn();
  useSelector.mockImplementation((selectorFn) => selectorFn(yourMockedStoreData));
  useDispatch.mockReturnValue(mockedDispatch);
  mount(<Router><Clients history={historyMock} /></Router>);
  expect(mockDispatch).toHaveBeenCalledWith(/*arguments your expect*/);
});

答案 2 :(得分:0)

import * as ReactRedux from 'react-redux'

describe('test', () => {
  it('should work', () => {
    const mockXXXFn = jest.fn()
    const spyOnUseDispatch = jest
      .spyOn(ReactRedux, 'useDispatch')
      .mockReturnValue({ xxxFn: mockXXXFn })

    // Do something ...

    expect(mockXXXFn).toHaveBeenCalledWith(...)

    spyOnUseDispatch.mockRestore()
  })
})

更新:请勿使用与Redux存储实现逻辑紧密耦合的React Redux hooks API,这使得测试非常困难。

答案 3 :(得分:0)

这是我使用React测试库解决的方法:

我有这个包装器,以便使用Provider渲染组件

export function configureTestStore(initialState = {}) {
  const store = createStore(
    rootReducer,
    initialState,
  );
  const origDispatch = store.dispatch;
  store.dispatch = jest.fn(origDispatch)

  return store;
}

/**
 * Create provider wrapper
 */
export const renderWithProviders = (
  ui,
  initialState = {},
  initialStore,
  renderFn = render,
) => {
  const store = initialStore || configureTestStore(initialState);

  const testingNode = {
    ...renderFn(
      <Provider store={store}>
        <Router history={history}>
          {ui}
        </Router>
      </Provider>
    ),
    store,
  };

  testingNode.rerenderWithProviders = (el, newState) => {
    return renderWithProviders(el, newState, store, testingNode.rerender);
  }

  return testingNode;
}

使用此方法,我可以从测试内部调用store.dispatch,并检查它是否以我想要的操作被调用。

  const mockState = {
    foo: {},
    bar: {}
  }

  const setup = (props = {}) => {
    return { ...renderWithProviders(<MyComponent {...props} />, mockState) }
  };

  it('should check if action was called after clicking button', () => {
    const { getByLabelText, store } = setup();

    const acceptBtn = getByLabelText('Accept all');
    expect(store.dispatch).toHaveBeenCalledWith(doActionStuff("DONE"));
  });