У меня есть простой компонент Todo, который использует хуки react-redux, которые я тестирую с помощью фермента, но я получаю либо ошибку, либо пустой объект с поверхностным рендерингом, как указано ниже.
Как правильно тестировать компоненты с помощью хуков из 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 Ошибка:
...
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 также терпят неудачу, поскольку wrapper является <Provider>, и вызов dive() на wrapper вернет ту же ошибку, что и v1.
Заранее спасибо за вашу помощь!
Похоже, это проблема конкретно с Enzyme, но пока не найдено адекватных обходных путей с использованием мелкого рендерера. Улучшенная поддержка хуков должна быть в следующем выпуске Enzyme: github.com/airbnb/enzyme/issues/2011
Используйте mount вместо мелкого, так как мелкое отображает только корневой компонент и размещает пользовательские дочерние компоненты как есть.





Я мог бы протестировать компонент, который использует редукционные хуки, используя средство монтирования ферментов и предоставляя фиктивное хранилище провайдеру:
Компонент
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;
Тестовое задание
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)
})
})
Кажется, что хуки Redux тесно связаны с компонентами Redux и React. И, обернув ваш компонент в Provider, разве вам не нужно было бы всегда проходить через него погружение()?
Я удивлен, почему это принятый ответ. Это вообще не отвечает на вопрос, использование mount не является альтернативой мелкому.
@omeralper Так как возникает вопрос: «Как протестировать компонент с помощью хуков react-redux?» Я думаю, да, это отвечает на вопрос.
Есть другой способ, чем @abidibo, если вы используете селектор функций, определенный в другом файле. Вы можете смоделировать useSelector и свою функцию выбора, а затем использовать shallow из фермента:
Компонент
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;
Селекторы
export const getIsSpinnerDisplayed = state => state.isSpinnerDisplayed;
Тестовое задание
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);
});
});
Это может быть немного хакерским, но это хорошо работает для меня :)
Кто-нибудь знает, как это сделать, используя Sinon вместо Jest? Я бился головой о стену, пытаясь заставить его работать так, как я думаю.
это сработало и для меня:
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();
});
Вы разместили тот же ответ, что и основной.
Чтобы издеваться над useSelector, можно сделать это
import * as redux from 'react-redux'
const spy = jest.spyOn(redux, 'useSelector')
spy.mockReturnValue({ username:'test' })
Так просто. Моя ошибка заключалась в том, что я передал имя редуктора spy.mockReturnValue()
Лучший вариант имхо, работает без сложной настройки и подходит для любого хука
ИМО, самый простой путь к самому быстрому ответу. Это хорошо работает, если вы используете jest.each``
как это будет работать, если компонент имеет несколько 'useSelector'r?
Может ли кто-нибудь продемонстрировать, как работать с несколькими useSelectors. Я только что начал с проекта реагирования на модульное тестирование. Пожалуйста, помогите!
Обходной путь для тестирования нескольких useSelector заключается в том, что вы можете создать хук со всеми селекторами, а затем протестировать этот пользовательский хук. Я знаю, что это не идеальное решение.
любое дополнительное решение для useDispatch?
@LarsHoldaas for useDispatch, вы можете попробовать что-то подобное, но я не совсем уверен в этом. const spyOnUseDispatch = jest.spyOn(redux, 'useDispatch') expect(spyOnUseDispatch).toHaveBeenCalled()
Это не работает с mount от Enzyme. Работает только для shallow рендеринга
Это МВА!
После поиска справки я объединил некоторые из найденных методов, чтобы издеваться над useSelector.
Сначала создайте функцию, которая выполняет некоторую загрузку перед тестом. Настройте хранилище с некоторыми значениями, которые вы хотите перезаписать, и смоделируйте функцию useSelector реакции-редукции.
Я думаю, что это действительно полезно для создания нескольких тестовых случаев, где вы видите, как состояние хранилища влияет на поведение вашего компонента.
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();
});
});
});
И компонент:
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;
Ниже код работает для меня.
import { configure, shallow} from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import ServiceListingScreen from './ServiceListingScreen';
import renderer from 'react-test-renderer';
import { createStore } from 'redux';
import serviceReducer from './../store/reducers/services';
import { Provider } from 'react-redux'
const store = createStore(serviceReducer ) ;
configure({adapter: new Adapter()});
const ReduxProvider = ({ children, reduxStore }) => (
<Provider store = {reduxStore}>{children}</Provider>
)
describe('Screen/ServiceListingScreen', () => {
it('renders correctly ', () => {
const wrapper = shallow(<Provider store = {store}><ServiceListingScreen /></Provider>);
const tree = renderer.create(wrapper).toJSON();
expect(tree).toMatchSnapshot();
});
});
Я думаю, что это и лучший, и самый простой способ пошутить над хуком useSelector из магазина Redux:
import * as redux from 'react-redux'
const user = {
id: 1,
name: 'User',
}
const state = { user }
jest
.spyOn(redux, 'useSelector')
.mockImplementation((callback) => callback(state))
Идея заключается в том, что вы можете издеваться только над подмножеством вашего магазина в state const.
Прочитав все ответы здесь и покопавшись в документации, я захотел объединить способы тестирования компонентов React с использованием хуков react-redux с Enzyme и мелкого рендеринга.
Эти тесты основаны на имитации хуков useSelector и useDispatch. Я также приведу примеры как в Jest, так и в Sinon.
Базовый пример шутки
import React from 'react';
import { shallow } from 'enzyme';
import * as redux from 'react-redux';
import TodoList from './TodoList';
describe('TodoList', () => {
let spyOnUseSelector;
let spyOnUseDispatch;
let mockDispatch;
beforeEach(() => {
// Mock useSelector hook
spyOnUseSelector = jest.spyOn(redux, 'useSelector');
spyOnUseSelector.mockReturnValue([{ id: 1, text: 'Old Item' }]);
// Mock useDispatch hook
spyOnUseDispatch = jest.spyOn(redux, 'useDispatch');
// Mock dispatch function returned from useDispatch
mockDispatch = jest.fn();
spyOnUseDispatch.mockReturnValue(mockDispatch);
});
afterEach(() => {
jest.restoreAllMocks();
});
it('should render', () => {
const wrapper = shallow(<TodoList />);
expect(wrapper.exists()).toBe(true);
});
it('should add a new todo item', () => {
const wrapper = shallow(<TodoList />);
// Logic to dispatch 'todoAdded' action
expect(mockDispatch.mock.calls[0][0]).toEqual({
type: 'todoAdded',
payload: 'New Item'
});
});
});
Базовый пример синона
import React from 'react';
import { shallow } from 'enzyme';
import sinon from 'sinon';
import * as redux from 'react-redux';
import TodoList from './TodoList';
describe('TodoList', () => {
let useSelectorStub;
let useDispatchStub;
let dispatchSpy;
beforeEach(() => {
// Mock useSelector hook
useSelectorStub = sinon.stub(redux, 'useSelector');
useSelectorStub.returns([{ id: 1, text: 'Old Item' }]);
// Mock useDispatch hook
useDispatchStub = sinon.stub(redux, 'useDispatch');
// Mock dispatch function returned from useDispatch
dispatchSpy = sinon.spy();
useDispatchStub.returns(dispatchSpy);
});
afterEach(() => {
sinon.restore();
});
// More testing logic...
});
Тестирование нескольких useSelectors требует, чтобы мы имитировали состояние приложения Redux.
var mockState = {
todos: [{ id: 1, text: 'Old Item' }]
};
Затем мы можем издеваться над собственной реализацией useSelector.
// Jest
const spyOnUseSelector = jest.spyOn(redux, 'useSelector').mockImplementation(cb => cb(mockState));
// Sinon
const useSelectorStub = sinon.stub(redux, 'useSelector').callsFake(cb => cb(mockState));
Это должны быть принятые ответы, поскольку они ясно показывают, как эффективно тестировать необходимые хуки.
искал более простой способ смоделировать несколько useSelectors вместо того, чтобы писать его, используя .mockImplementationOnce n раз. спасибо @Andrew W
Вы можете попробовать redux-saga-test-plan крутое избыточное утверждение и работающую библиотеку, а также легкий вес, он автоматически выполняет всю вашу тяжелую работу по запуску саги и утверждению.
const mockState = { rick:"Genius", morty:"dumbAsss"}
expectSaga(yourCoolSaga)
.provide({
select({ selector }, next) {
if (selector) {
return mockState;
}
return next();
}
})
// some assertion here
.put(actions.updateRickMortyPowers(mockState))
.silentRun();
Вы когда-нибудь догадывались об этом? Я столкнулся с той же проблемой после перехода на хуки Redux.