在开玩笑中模拟 useDispatch 并使用函数组件中的调度操作来测试参数



嗨,我正在使用笑话和酶编写功能组件的测试。 和 当我模拟单击时,组件的参数(使用 useState 的组件状态(会发生变化。 当状态更改时,请使用效果调用,并且在使用效果中,我在更改后使用参数调度一些异步操作。所以我想用我正在调度动作来测试参数。 为此,我想模拟调度。我怎样才能做到这一点? 任何人都可以帮助我,提前感谢。下面我分享代码。

组件.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}
/>
</>
);
};

组件测试.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')

})
});

[upd] 从那以后,我的想法发生了很大的变化。现在我认为模拟商店(带有redux-mock-store甚至改变其状态的真实商店(-并使用<Provider store={mockedStore}>包装组件-更加可靠和方便。查看下面的另一个答案。

如果您模拟react-redux您将能够验证useDispatch调用的参数。同样在这种情况下,您将需要重新创建useSelector的逻辑(这非常简单,实际上您不必使 mock 成为钩子(。同样,使用这种方法,您根本不需要模拟商店或<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*/);
});
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();
})
}});

从功能组件开始,我们像上面一样模拟调度以阻止它以执行真正的实现。希望对您有所帮助!

这就是我使用 react 测试库解决的方式:

我有这个包装器来使用提供程序渲染组件

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"));
});

我看到了使用实际<Provider store={store}>的优势:

  • 编写测试更容易
  • 更具可读性,因为Just Store的数据实际上是被模拟的(一个模拟而不是多个 - 有时不一致 -useDispatchuseSelector的模拟(

但是引入带有真实化简器和真实调度的真实商店对我来说对于单元测试来说似乎是矫枉过正(但对于集成测试来说是可以的(:

  • 模拟所有服务器请求可能是一项艰巨的任务
  • 通常,我们已经通过基于每个切片的测试来涵盖该逻辑

考虑到这一点,从redux-mock-store中挑选了configureStore而不是redux,并得到了下一个助手(使用酶(:

import { act } from 'react-dom/test-utils';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { mount } from 'enzyme';
import { Provider } from 'react-redux';
function renderInRedux(
children,
initialData = {}
) {
let state = initialData;
const store = (configureMockStore([thunk]))(() => state);
const wrapper = mount(
<Provider store={store}>
{children}
</Provider>
);
return {
/*
since Enzyme wrappers are readonly, we need retrieve target element in unit test each time after any interaction
*/
getComponent() {
return wrapper.childAt(0);
},
/*
set store to any desired config; previous value is replaced;
*/
replaceStore(newState) {
act(() => {
state = newState;
store.dispatch({ type: dummyActionTypeName }); // just to trigger listeners
});
wrapper.update();
},
/*
bridge to redux-mock-store's getActions
*/
getActions() {
return store.getActions().filter(({ type }) => type !== dummyActionTypeName);
},
/*
bridge to redux-mock-store's clearActions()
*/
clearActions() {
return store.clearActions();
},
};
}

和使用示例:

const {
getComponent,
replaceStore,
} = renderInRedux(<Loader />, { isRequesting: false });
expect(getComponent().isEmptyRender()).toBeTruthy();
replaceStore({ isRequesting: true });
expect(getComponent().isEmptyRender()).toBeFalsy();

但是,如果我们想测试调度,如何帮助避免嘲笑服务器端交互?好吧,它本身没有。但是我们可以通过简单的方式模拟和测试动作调度:

import { saveThing as saveThingAction } from '../myActions.js';
jest.mock('../myActions.js', () => ({
saveThing: jest.fn().mockReturnValue({ type: 'saveThing' })
}));
beforeEach(() => {
});
....
const { getComponent, getActions } = renderInRedux(
<SomeForm />, 
someMockedReduxStore
); 
getComponent().find(Button).simulate('click');
expect(getActions()).toContainEqual(saveThingAction());
expect(saveThingAction).toHaveBeenCalledWith(someExpectedArguments);
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钩子API,这使得测试变得非常困难。

最新更新