首页 > 解决方案 > 模拟redux后反应组件未设置状态

问题描述

这是我的测试

const initialRootState = {
  accounts: [mockAccounts],
  isLoading: false
}

describe('Account Dashboard', () => {
  let rootState = {
    ...initialRootState
  }

  const mockStore = configureStore()
  const store = mockStore({ ...rootState })
  const mockFunction = jest.fn()

  jest.spyOn(Redux, 'useDispatch').mockImplementation(() => mockFunction)
  jest
    .spyOn(Redux, 'useSelector')
    .mockImplementation((state) => state(store.getState()))

  afterEach(() => {
    mockFunction.mockClear()

    // Reseting state
    rootState = {
      ...initialRootState
    }
  })

  it('renders correctly', () => {
    const wrapper = mount(
        <TestWrapper>
          <AccountDashboard />
        </TestWrapper>
    )
    console.log(wrapper)
  })
})

在我的组件中,我正在映射来自该州的帐户。在我的测试中,我收到以下错误TypeError: Cannot read property 'map' of undefined

我想测试if statement我在组件中使用的一个,以确保它根据我收到的帐户数量返回正确的视图。

但是,当我console.log(store.getState())正确打印时。我究竟做错了什么?

标签: reactjsreduxjestjs

解决方案


如果你要测试一个 Redux 连接的组件,我建议不要模拟它的内部,而是把它当作一个连接到真实 Redux 存储的 React 组件来测试。

例如,这是一个用于安装连接组件的工厂函数enzyme

实用程序/withRedux.jsx

import * as React from "react";
import { createStore } from "redux";
import { Provider } from "react-redux";
import { mount } from "enzyme";
import rootReducer from "../path/to/reducers";

/*
  You can skip recreating this "store" by importing/exporting
  the real "store" from wherever you defined it in your app
*/
export const store = createStore(rootReducer);

/**
 * Factory function to create a mounted Redux connected wrapper for a React component
 *
 * @param {ReactNode} Component - the Component to be mounted via Enzyme
 * @function createElement - Creates a wrapper around the passed in component with incoming props so that we can still use "wrapper.setProps" on the root
 * @returns {ReactWrapper} - a mounted React component with a Redux store.
 */
export const withRedux = Component =>
  mount(
    React.createElement(props => (
      <Provider store={store}>
        {React.cloneElement(Component, props)}
      </Provider>
    )),
    options
  );

export default withRedux;

现在,使用上面的工厂函数,我们可以通过简单地使用来测试连接的组件store.dispatch

测试/ConnectedComponent.jsx

import * as React from "react";
import withRedux, { store } from "../path/to/utils/withRedux";
import ConnectedComponent from "../index";

const fakeAccountData = [{...}, {...}, {...}];

describe("Connected Component", () => {
  let wrapper;
  beforeEach(() => {
    wrapper = withRedux(<ConnectedComponent />);
  });

  it("initially shows a loading indicator", () => {
    expect(wrapper.find(".loading-indicator")).exists().toBeTruthy();
  });

  it("displays the accounts when data is present", () => {
    /*
      Ideally, you'll be dispatching an action function for simplicity
      
      For example: store.dispatch(setAccounts(fakeAccountData));

      But for ease of readability, I've written it out below.
    */
    store.dispatch({ type: "ACCOUNTS/LOADED", accounts: fakeAccountData }));

    // update the component to reflect the prop changes
    wrapper.update();

    expect(wrapper.find(".loading-indicator")).exists().toBeFalsy();
    expect(wrapper.find(".accounts").exists()).toBeTruthy();
  });
});

当您开始测试其他 Redux 连接的组件时,这极大地简化了不必一遍又一遍地模拟 store/useSelector/useDispatch。


connect附带说明一下,如果您在导出未连接的组件时使用 react-redux 的功能,则可以完全跳过此步骤。您可以在测试中导入未连接的组件,而不是导入默认导出...

示例组件:

import * as React from "react";
import { connect } from "react-redux";

export const Example = ({ accounts, isLoading }) => { ... };

const mapStateToProps = state => ({ ... });

const mapDispatchToProps = { ... };

export default connect(mapStateToProps, mapDispatchToProps)(Example);

示例测试:

import * as React from "react";
import { mount } from "enzyme";
import { Example } from "../index";

const initProps = {
  accounts: [],
  isLoading: true
};

const fakeAccountData = [{...}, {...}, {...}];

describe("Unconnected Example Component", () => {
  let wrapper;
  beforeEach(() => {
    wrapper = mount(<Example {...initProps } />);
  });

  it("initially shows a loading indicator", () => {
    expect(wrapper.find(".loading-indicator")).exists().toBeTruthy();
  });

  it("displays the accounts when data is present", () => {
    wrapper.setProps({ accounts: fakeAccountData, isLoading: false });
    wrapper.update();

    expect(wrapper.find(".loading-indicator")).exists().toBeFalsy();
    expect(wrapper.find(".accounts").exists()).toBeTruthy();
  });
});

推荐阅读