在 React 和 Redux 中使用 Enzyme 进行嵌套组件测试

Nested components testing with Enzyme inside of React & Redux

我有一个组件 SampleComponent 安装另一个 "connected component"(即 container)。当我尝试通过 mounting 测试 SampleComponent 时(因为我需要 componentDidMount),我收到错误消息:

Invariant Violation: Could not find "store" in either the context or props of "Connect(ContainerComponent)". Either wrap the root component in a , or explicitly pass "store" as a prop to "Connect(ContainerComponent)".

最好的测试方法是什么?

我基本上所做的是引入我的 redux 商店(和 Provider)并将其包装在实用程序组件中,如下所示:

export const CustomProvider = ({ children }) => {
  return (
    <Provider store={store}>
      {children}
    </Provider>
  );
};

然后,我 mount SampleComponent 和 运行 测试:

it('contains <ChildComponent/> Component', () => {
  const wrapper = mount(
    <CustomProvider>
      <SampleComponent {...defaultProps} />
    </CustomProvider>
  );
  expect(wrapper.find(ChildComponent)).to.have.length(1);
});

Enzyme 的装载采用可选参数。您需要的两个是

options.context: (Object [optional]): Context to be passed into the component

options.childContextTypes: (Object [optional]): Merged contextTypes for all children of the wrapper 您将使用如下选项对象挂载 SampleComponent

const store = { 
  subscribe: () => {},
  dispatch: () => {},
  getState: () => ({ ... whatever state you need to pass in ... })
}
const options = {
  context: { store }, 
  childContextTypes: { store: React.PropTypes.object.isRequired } 
}

const _wrapper = mount(<SampleComponent {...defaultProps} />, options)

现在您的 SampleComponent 会将您提供的上下文传递给 connected component

您可以使用名称导出来解决这个问题:

你应该有:

class SampleComponent extends React.Component{
...
   render(){
       <div></div>
   }
}

export default connect(mapStateToProps, mapDispatchToProps)(SampleComponent)

您可以在 class 之前添加导出:

export class SampleComponent extends React.Component{

并在没有 redux store 的情况下导入此组件:

import { SampleComponent } from 'your-path/SampleComponent';

使用此解决方案,您无需将商店导入测试文件。

选项 1)

您可以在测试中使用 React-Redux 的 Provider 组件包装容器组件。因此,通过这种方法,您实际上引用了商店,将其传递给提供者,并在内部编写您的待测组件。这种方法的优点是您实际上可以为测试创建自定义商店。如果您想测试组件中与 Redux 相关的部分,这种方法很有用。

选项 2)

也许你并不关心测试 Redux 相关的部分。如果您只是对测试组件的呈现和本地状态相关的行为感兴趣,您可以简单地为组件的未连接的普通版本添加一个命名导出。只是为了澄清一下,当您将 "export" 关键字添加到 class 时,基本上您是在说现在 class 可以使用花括号 {} 或不使用两种方式导入。示例:

export class MyComponent extends React.Component{ render(){ ... }}

...

export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)

稍后在您的测试文件上:

import MyComponent from 'your-path/MyComponent'; // it needs a store because you use "default export" with connect
import {MyComponent} from 'your-path/MyComponent'; // don't need store because you use "export" on top of your class.

我希望能帮助到那里的任何人。

为了使装饰器语法的使用更易于测试,我做了这个: https://www.npmjs.com/package/babel-plugin-undecorate

输入:

@anyOldClassDecorator
export class AnyOldClass {
  @anyOldMethodDecorator
  method() {
    console.log('hello');   
  }
}

输出:

@anyOldClassDecorator
export class AnyOldClass {
  @anyOldMethodDecorator
  method() {
    console.log('hello');   
  }
}

export class __undecorated__AnyOldClass {
  method() {
    console.log('hello');   
  }
}

希望这可以提供一个可靠的选项 3!

还有使用redux-mock-store的选项。

A mock store for testing Redux async action creators and middleware. The mock store will create an array of dispatched actions which serve as an action log for tests.

模拟商店提供了 Redux 所需的商店对象的必要方法。 您可以指定可选的中间件和您的应用特定的初始状态。

import configureStore from 'redux-mock-store'

const middlewares = []
const mockStore = configureStore(middlewares)

const initialState = {}
const store = mockStore(initialState)

const wrapper = mount(<SampleComponent store={store}/>)