我想测试一个功能组件,下面是:
export const MyFunctionalComponent=()=>{
const {id, otherid} = useSelector((state) => state.user);
const accounts = useSelector((state) => state.Selection.accounts);
const dispatch = useDispatch();
useEffect(() => {
if (!accounts) {
dispatch(getDetails(id, otherid));
}
}, []);
const handleOnClick = (Id) => {
dispatch(saveId(Id));
};
if (!accounts) {
return <CircularLoader />;
}
if (!accounts.length) {
return (
<AccessUnavailablePanel
/>
);
}
if (accounts.length === 1) {
return <Redirect to={`${accounts[0].id}`} />;
}
return (
<MyList
accounts={accounts}
handleOnClick={handleOnClick}
/>
);
};
我是JEST的新手,我如何正确地模拟useSelector, useDispatch和useEffect ?
我试着测试如下:
jest.mock('react-redux', () => ({
...jest.requireActual('react-redux'),
useSelector: jest.fn(),
useDispatch: jest.fn()
}));
describe('<MyFunctionalComponent/>', () => {
const useEffect=jest.spyOn(React, "useEffect").mockImplementation(() => {})
const useSelectorMock = reactRedux.useSelector;
const useDispatchMock = reactRedux.useDispatch;
const user = {
id: '32sdfsdfr-fjdfdk33-4434sdfs',
otherid: '73587hdfjg-dfghd94-jgdj'
};
const store = mockStore({
user:user,
Selection: {
Id: '',
accounts: null
}
});
let wrapper;
const setup = () => {
const {result} = renderHook(() => MyFunctionalComponent(), {
wrapper: ({children}) => (
<Provider store={store}>{children}</Provider>
)
});
return result;
};
describe('Rendering accounts available', () => {
beforeEach(() => {
useDispatchMock.mockImplementation(() => () => {});
useSelectorMock.mockImplementation((selector) =>
selector(mockStore)
);
});
afterEach(() => {
jest.clearAllMocks();
useSelectorMock.mockClear();
useDispatchMock.mockClear();
});
it('should render the accounts if more than 1 account available', () => {
useSelectorMock.mockReturnValue(user);
const mockdata = {
accounts: [
{
id: '637ghgh',
},
{
id: '10a190abd',
}
]
};
const getDetails = jest.spyOn(
SelectionActions,
'getDetails'
);
useSelectorMock.mockReturnValue(mocdata);
useDispatchMock.mockReturnValue(
getDetails(user.id, user.otherid)
);
wrapper = setup();
store.dispatch(getDetails(user.id, user.otherid))
sleep(500);
const actions=store.getActions();
console.debug(actions[0])
expect(getDetails).toHaveBeenCalledWith(user.id, user.otherid);
expect(actions[0].type).toEqual('GET_DETAILS')
expect(wrapper.current).toMatchSnapshot();
});
});
但是我的快照似乎返回accessununavailable,并且有效负载未定义。如何修改测试以正确地将模拟数据作为有效负载返回并获得正确的快照?
可以通过模拟库(如react-redux
)进行测试,但不建议使用。它过于关注实现细节。更好的方法是使用模拟存储只提供模拟数据,而不提供模拟实现细节。使用原始的、未模拟的useSelector
和useEffect
钩子,它们更接近代码的实际功能,而不是模拟的实现细节。
此外,创建模拟对象及其实现也是非常繁琐的步骤。您还应该注意清理模拟对象,以避免被其他测试用例使用,从而导致测试失败。
使用redux-mock-store创建具有模拟状态的模拟存储。
每个测试用例提供不同的模拟数据来驱动组件呈现,最后断言组件呈现是否正确。
为简单起见,只是为了演示这个测试方法,我用一个简单的组件替换了您的组件,原理是一样的。
。
index.tsx
:
import { useSelector, useDispatch } from 'react-redux';
import { useEffect } from 'react';
import React from 'react';
const getDetails = (id, otherId) => ({ type: 'GET_DETAILS' });
export const MyFunctionalComponent = () => {
const { id, otherid } = useSelector((state: any) => state.user);
const accounts = useSelector((state: any) => state.Selection.accounts);
const dispatch = useDispatch();
useEffect(() => {
if (!accounts) {
dispatch(getDetails(id, otherid));
}
}, []);
if (!accounts) {
return <div>CircularLoader</div>;
}
if (!accounts.length) {
return <div>AccessUnavailablePanel</div>;
}
if (accounts.length === 1) {
return <div>Redirect</div>;
}
return <div>MyList</div>;
};
index.test.tsx
:
import { MyFunctionalComponent } from './';
import createMockStore from 'redux-mock-store';
import { screen, render } from '@testing-library/react';
import { Provider } from 'react-redux';
import React from 'react';
const mockStore = createMockStore([]);
describe('69013562', () => {
test('should render AccessUnavailablePanel', () => {
const state = {
user: { id: '1', otherid: '2' },
Selection: { accounts: [] },
};
const store = mockStore(state);
render(<MyFunctionalComponent />, {
wrapper: ({ children }) => <Provider store={store}>{children}</Provider>,
});
expect(screen.getAllByText('AccessUnavailablePanel')).toBeTruthy();
expect(store.getActions()).toEqual([]);
});
test('should render CircularLoader and dispatch get details action', () => {
const state = {
user: { id: '1', otherid: '2' },
Selection: {},
};
const store = mockStore(state);
render(<MyFunctionalComponent />, {
wrapper: ({ children }) => <Provider store={store}>{children}</Provider>,
});
expect(screen.getAllByText('CircularLoader')).toBeTruthy();
expect(store.getActions()).toEqual([{ type: 'GET_DETAILS' }]);
});
test('should render Redirect', () => {
const state = {
user: { id: '1', otherid: '2' },
Selection: { accounts: [1] },
};
const store = mockStore(state);
render(<MyFunctionalComponent />, {
wrapper: ({ children }) => <Provider store={store}>{children}</Provider>,
});
expect(screen.getAllByText('Redirect')).toBeTruthy();
expect(store.getActions()).toEqual([]);
});
test('should render MyList', () => {
const state = {
user: { id: '1', otherid: '2' },
Selection: { accounts: [1, 2, 3] },
};
const store = mockStore(state);
render(<MyFunctionalComponent />, {
wrapper: ({ children }) => <Provider store={store}>{children}</Provider>,
});
expect(screen.getAllByText('MyList')).toBeTruthy();
expect(store.getActions()).toEqual([]);
});
});
测试结果:
PASS examples/69013562/index.test.tsx (10.158 s)
69013562
✓ should render AccessUnavailablePanel (28 ms)
✓ should render CircularLoader and dispatch get details action (3 ms)
✓ should render Redirect (2 ms)
✓ should render MyList (2 ms)
-----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
index.tsx | 100 | 100 | 100 | 100 |
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 4 passed, 4 total
Snapshots: 0 total
Time: 11.014 s
没有单一答案的考试策略,要根据具体情况进行调整。