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

cro*_*ays 14 testing reactjs redux enzyme react-redux

我有一个简单的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>
  );
};
Run Code Online (Sandbox Code Playgroud)

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);
});
Run Code Online (Sandbox Code Playgroud)

v1错误:

...
Invariant Violation: could not find react-redux context value; 
please ensure the component is wrapped in a <Provider>
...
Run Code Online (Sandbox Code Playgroud)

Todos.test.js v2

...
Invariant Violation: could not find react-redux context value; 
please ensure the component is wrapped in a <Provider>
...
Run Code Online (Sandbox Code Playgroud)

V2测试也失败,因为wrapper<Provider>和调用dive()wrapper会返回相同的错误为V1。

在此先感谢您的帮助!

Kri*_*iti 46

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

import * as redux from 'react-redux'

const spy = jest.spyOn(redux, 'useSelector')
spy.mockReturnValue({ username:'test' })
Run Code Online (Sandbox Code Playgroud)

  • 任何人都可以演示如何处理多个 useSelector。我刚刚开始单元测试反应项目。请有人帮忙! (4认同)
  • `TypeError:无法重新定义属性:useSelector` 我说对了 (3认同)
  • IMO 的最佳选择,无需任何复杂的设置即可工作,并且适用于每个钩子 (2认同)
  • 如果一个组件有多个“useSelecto”,它将如何工作? (2认同)

abi*_*ibo 10

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

零件

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;
Run Code Online (Sandbox Code Playgroud)

测试

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)
  })
})
Run Code Online (Sandbox Code Playgroud)

  • 看起来 Redux hooks 将 Redux 与 React 组件紧密耦合在一起。通过将组件包装在 Provider 中,您不是总是需要通过它进行 div 操作吗? (4认同)
  • 我很惊讶为什么这是被接受的答案。这根本没有回答问题,使用 mount 并不能替代shallow。 (2认同)

And*_*w W 9

使用 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'
    });
  });
});
Run Code Online (Sandbox Code Playgroud)

基本的诗浓示例

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...
});
Run Code Online (Sandbox Code Playgroud)

测试多个 useSelector Hooks

测试多个useSelectors需要我们模拟 Redux 应用程序状态。

var mockState = {
  todos: [{ id: 1, text: 'Old Item' }]
};
Run Code Online (Sandbox Code Playgroud)

然后我们可以模拟我们自己的useSelector.

// Jest
const spyOnUseSelector = jest.spyOn(redux, 'useSelector').mockImplementation(cb => cb(mockState));

// Sinon
const useSelectorStub = sinon.stub(redux, 'useSelector').callsFake(cb => cb(mockState));
Run Code Online (Sandbox Code Playgroud)


Paw*_*cki 8

useSelector我认为这是在玩笑中从 Redux store模拟 hook 的最好也是最简单的方法:

  import * as redux from 'react-redux'

  const user = {
    id: 1,
    name: 'User',
  }

  const state = { user }

  jest
    .spyOn(redux, 'useSelector')
    .mockImplementation((callback) => callback(state))
Run Code Online (Sandbox Code Playgroud)

其想法是,您可以state仅使用商店数据的子集来提供商店模拟。


jhu*_*hul 6

如果您使用另一个文件中定义的函数选择器,则@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;
Run Code Online (Sandbox Code Playgroud)

选择器

export const getIsSpinnerDisplayed = state => state.isSpinnerDisplayed;
Run Code Online (Sandbox Code Playgroud)

测试

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);
  });
});
Run Code Online (Sandbox Code Playgroud)

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