可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have a simple Todo component that utilizes react-redux hooks that I'm testing using enzyme but I'm getting either an error or an empty object with a shallow render as noted below.
What is the correct way to test components using hooks from react-redux?
Todos.js
const Todos = () => {
const { todos } = useSelector(state => state);
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
);
};
Todos.test.js v1
...
it('renders without crashing', () => {
const wrapper = shallow(<Todos />);
expect(wrapper).toMatchSnapshot();
});
it('should render a ul', () => {
const wrapper = shallow(<Todos />);
expect(wrapper.find('ul').length).toBe(1);
});
v1 Error:
...
Invariant Violation: could not find react-redux context value;
please ensure the component is wrapped in a <Provider>
...
Todos.test.js v2
...
// imported Provider from react-redux
it('renders without crashing', () => {
const wrapper = shallow(
<Provider store={store}>
<Todos />
</Provider>,
);
expect(wrapper).toMatchSnapshot();
});
it('should render a ul', () => {
const wrapper = shallow(<Provider store={store}><Todos /></Provider>);
expect(wrapper.find('ul').length).toBe(1);
});
v2 tests also fail since wrapper
is the <Provider>
and calling dive()
on wrapper
will return the same error as v1.
Thanks in advance for your help!
回答1:
I could test a component which uses redux hooks using enzyme mount facility and providing a mocked store to the Provider:
Component
import React from 'react';
import AppRouter from './Router'
import { useDispatch, useSelector } from 'react-redux'
import StartupActions from './Redux/Startup'
import Startup from './Components/Startup'
import './App.css';
// This is the main component, it includes the router which manages
// routing to different views.
// This is also the right place to declare components which should be
// displayed everywhere, i.e. sockets, services,...
function App () {
const dispatch = useDispatch()
const startupComplete = useSelector(state => state.startup.complete)
if (!startupComplete) {
setTimeout(() => dispatch(StartupActions.startup()), 1000)
}
return (
<div className="app">
{startupComplete ? <AppRouter /> : <Startup />}
</div>
);
}
export default App;
Test
import React from 'react';
import {Provider} from 'react-redux'
import { mount, shallow } from 'enzyme'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk';
import App from '../App';
const mockStore = configureMockStore([thunk]);
describe('App', () => {
it('should render a startup component if startup is not complete', () => {
const store = mockStore({
startup: { complete: false }
});
const wrapper = mount(
<Provider store={store}>
<App />
</Provider>
)
expect(wrapper.find('Startup').length).toEqual(1)
})
})
回答2:
There is another way than @abidibo if you use a function selector defined in another file. You can mock useSelector
and your selector function, and then use shallow
from enzyme:
Component
import * as React from 'react';
import { useSelector } from 'react-redux';
import Spinner from './Spinner';
import Button from './Button ';
import { getIsSpinnerDisplayed } from './selectors';
const Example = () => {
const isSpinnerDisplayed = useSelector(getIsSpinnerDisplayed);
return isSpinnerDisplayed ? <Spinner /> : <Button />;
};
export default Example;
Selectors
export const getIsSpinnerDisplayed = state => state.isSpinnerDisplayed;
Test
import * as React from 'react';
import { shallow } from 'enzyme';
import Example from './Example';
import Button from './Button ';
import { getIsSpinnerDisplayed } from './selectors';
jest.mock('react-redux', () => ({
useSelector: jest.fn(fn => fn()),
}));
jest.mock('./selectors');
describe('Example', () => {
it('should render Button if getIsSpinnerDisplayed returns false', () => {
getIsSpinnerDisplayed.mockReturnValue(false);
const wrapper = shallow(<Example />);
expect(wrapper.find(Button).exists()).toBe(true);
});
});
It may be a little bit hacky, but it works well for me :)
回答3:
To mock useSelector use can do this
import * as redux from 'react-redux'
const spy = jest.spyOn(redux, 'useSelector')
spy.mockReturnValue({ username:'test' })
回答4:
After searching for help I combined some of the methods I found to mock useSelector.
First create a function that does some bootstrapping before your test.
Setting up the store with some values that you want to overwrite and mock the useSelector function of react-redux.
I think it is really useful for creating multiple testcases where u see how the store state influences the behaviour of your component.
import configureMockStore from 'redux-mock-store';
import * as Redux from 'react-redux';
import MyComponent from './MyComponent';
const mockSelectors = (storeValues) => {
const mockStore = configureMockStore()({
mobile: {
isUserConnected: false
...storeValues
},
});
jest
.spyOn(Redux, 'useSelector')
.mockImplementation(state => state.dependencies[0](mockStore.getState()));
};
describe('isUserConnected: true', () => {
beforeEach(() => {
mockSelectors({ isUserConnected: true });
component = shallow(<MyComponent />);
test('should render a disconnect button', () => {
expect(component).toBeDefined();
expect(component.find('button')).toBeTruthy();
});
});
});
And the component:
import React from 'react';
import { shallowEqual, useSelector } from 'react-redux';
const MyComponent = () => {
const isConnected = useSelector(selectIsConnected, shallowEqual);
return (
<>
{
showDisconnect ? (
<button type="button" onClick={() => ()}>disconnect</button>
) : null
}
</>
);
};
export default MyComponent;
回答5:
this is worked for me as well :
import { shallow, mount } from "enzyme";
const store = mockStore({
startup: { complete: false }
});
describe("==== Testing App ======", () => {
const setUpFn = props => {
return mount(
<Provider store={store}>
<App />
</Provider>
);
};
let wrapper;
beforeEach(() => {
wrapper = setUpFn();
});