如何使用 react-redux 钩子测试组件?

IT技术 reactjs testing redux react-redux enzyme
2021-03-28 17:30:09

我有一个简单的 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。

在此先感谢您的帮助!

6个回答

模拟 useSelector 使用可以做到这一点

import * as redux from 'react-redux'

const spy = jest.spyOn(redux, 'useSelector')
spy.mockReturnValue({ username:'test' })
IMO 的最佳选择,无需任何复杂设置即可工作,适用于每个钩子
2021-05-23 17:30:09
很简单。我的错误是传递减速器名称spy.mockReturnValue()
2021-05-25 17:30:09
如果一个组件有多个“useSelecto”,它会如何工作?
2021-05-25 17:30:09
IMO,获得最快答案的最简单途径。如果您使用 jest.each`` 这很有效
2021-06-01 17:30:09
任何人都可以演示如何处理多个 useSelectors。我刚刚开始进行单元测试反应项目。请有人帮忙!
2021-06-20 17:30:09

我可以使用酶安装工具测试使用 redux 钩子的组件,并向提供程序提供模拟存储:

零件

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)
  })
})
@omeralper 因为问题是“如何使用 react-redux 钩子测试组件?” 我想是的,它回答了这个问题。
2021-06-09 17:30:09
我很惊讶为什么这是公认的答案。这根本不能回答问题,使用 mount 不能替代浅层。
2021-06-10 17:30:09
似乎 Redux hooks 将 Redux 与 React 组件紧密耦合。并且通过将您的组件包装在一个 Provider 中,您是不是总是需要通过它进行 dive() ?
2021-06-15 17:30:09

如果您使用在另一个文件中定义的函数选择器,还有另一种方法而不是 @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);
  });
});

它可能有点hacky,但对我来说效果很好:)

有谁知道如何使用 Sinon 而不是 Jest 来做到这一点?我一直在用头撞墙,试图让它像我认为的那样工作。
2021-05-25 17:30:09

使用 Enzyme 的浅渲染测试 React Redux Hooks

在阅读了这里的所有回复并挖掘了文档之后,我想汇总使用 react-redux hooks with Enzyme 和浅渲染来测试 React 组件的方法。

这些测试依赖于模拟useSelectoruseDispatch钩子。我还将在 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...
});

测试多个 useSelector Hooks

测试多个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,而不是使用.mockImplementationOncen 次编写它谢谢@Andrew W
2021-05-22 17:30:09
这应该是公认的答案,因为它清楚地涵盖了如何有效地测试所需的钩子。
2021-06-21 17:30:09

下面的代码对我有用。

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