javascript 如何使用浅渲染测试装饰的 React 组件

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/31459720/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-28 13:50:06  来源:igfitidea点击:

How to test decorated React component with shallow rendering

javascriptreactjsmochareact-jsxjestjs

提问by eguneys

I am following this tutorial: http://reactkungfu.com/2015/07/approaches-to-testing-react-components-an-overview/

我正在关注本教程:http: //reactkungfu.com/2015/07/approaches-to-testing-react-components-an-overview/

Trying to learn how "shallow rendering" works.

试图了解“浅层渲染”的工作原理。

I have a higher order component:

我有一个高阶组件:

import React from 'react';

function withMUI(ComposedComponent) {
  return class withMUI {
    render() {
      return <ComposedComponent {...this.props}/>;
    }
  };
}

and a component:

和一个组件:

@withMUI
class PlayerProfile extends React.Component {
  render() {
    const { name, avatar } = this.props;
    return (
      <div className="player-profile">
        <div className='profile-name'>{name}</div>
        <div>
          <Avatar src={avatar}/>
        </div>
      </div>
    );
  }
}

and a test:

和一个测试:

describe('PlayerProfile component - testing with shallow rendering', () => {
  beforeEach(function() {
   let {TestUtils} = React.addons;

    this.TestUtils = TestUtils;

    this.renderer = TestUtils.createRenderer();
    this.renderer.render(<PlayerProfile name='user'
                                            avatar='avatar'/>);
  });

  it('renders an Avatar', function() {
    let result = this.renderer.getRenderOutput();
    console.log(result);
    expect(result.type).to.equal(PlayerProfile);
  });
});

The resultvariable holds this.renderer.getRenderOutput()

result变量保存this.renderer.getRenderOutput()

In the tutorial the result.typeis tested like:

在教程result.type中测试如下:

expect(result.type).toEqual('div');

expect(result.type).toEqual('div');

in my case, if I log the resultit is:

就我而言,如果我记录result它是:

LOG: Object{type: function PlayerProfile() {..}, .. }

LOG: Object{type: function PlayerProfile() {..}, .. }

so I changed my test like:

所以我改变了我的测试:

expect(result.type).toEqual(PlayerProfile)

expect(result.type).toEqual(PlayerProfile)

now it gives me this error:

现在它给了我这个错误:

Assertion Error: expected [Function: PlayerProfile] to equal [Function: withMUI]

Assertion Error: expected [Function: PlayerProfile] to equal [Function: withMUI]

So PlayerProfile's type is the higher order function withMUI.

SoPlayerProfile的类型是高阶函数withMUI

PlayerProfiledecorated with withMUI, using shallow rendering, only the PlayerProfilecomponent is rendered and not it's children. So shallow rendering wouldn't work with decorated components I assume.

PlayerProfile装饰withMUI,使用浅渲染,只渲染PlayerProfile组件而不是它的子组件。所以浅渲染不适用于我假设的装饰组件。

My question is:

我的问题是:

Why in the tutorial result.typeis expected to be a div, but in my case isn't.

为什么在教程result.type中应该是一个 div,但在我的情况下不是。

How can I test a React component decorated with higher order component using shallow rendering?

如何使用浅渲染测试用高阶组件装饰的 React 组件?

回答by Brigand

You can't. First let's slightly desugar the decorator:

你不能。首先让我们稍微对装饰器进行脱糖:

let PlayerProfile = withMUI(
    class PlayerProfile extends React.Component {
      // ...
    }
);

withMUI returns a different class, so the PlayerProfile class only exists in withMUI's closure.

withMUI 返回一个不同的类,所以 PlayerProfile 类只存在于 withMUI 的闭包中。

This is here's a simplified version:

这是一个简化版本:

var withMUI = function(arg){ return null };
var PlayerProfile = withMUI({functionIWantToTest: ...});

You pass the value to the function, it doesn't give it back, you don't have the value.

您将值传递给函数,它不会将其返回,您没有该值。

The solution? Hold a reference to it.

解决方案?保留对它的引用。

// no decorator here
class PlayerProfile extends React.Component {
  // ...
}

Then we can export both the wrapped and unwrapped versions of the component:

然后我们可以导出组件的包装和未包装版本:

// this must be after the class is declared, unfortunately
export default withMUI(PlayerProfile);
export let undecorated = PlayerProfile;

The normal code using this component doesn't change, but your tests will use this:

使用这个组件的普通代码不会改变,但你的测试将使用这个:

import {undecorated as PlayerProfile} from '../src/PlayerProfile';


The alternative is to mock the withMUI function to be (x) => x(the identity function). This may cause weird side effects and needs to be done from the testing side, so your tests and source could fall out of sync as decorators are added.

另一种方法是将 withMUI 函数模拟为(x) => x(身份函数)。这可能会导致奇怪的副作用,需要从测试端完成,因此随着装饰器的添加,您的测试和源可能会不同步。

Not using decorators seems like the safe option here.

不使用装饰器在这里似乎是安全的选择。

回答by Kamaraju

Use Enzyme to test higher order / decorators with Shallow with a method called dive()

使用 Enzyme 测试带有 Shallow 的高阶/装饰器,并使用一种称为“dive()”的方法

Follow this link, to see how dive works

按照这个链接,看看潜水是如何工作的

https://github.com/airbnb/enzyme/blob/master/docs/api/ShallowWrapper/dive.md

https://github.com/airbnb/enzyme/blob/master/docs/api/ShallowWrapper/dive.md

So you can shallow the component with higher order and then dive inside.

所以你可以用更高的顺序浅化组件,然后深入内部。

In the above example :

在上面的例子中:

const wrapper=shallow(<PlayerProfile name={name} avatar={}/>)
expect(wrapper.find("PlayerProfile").dive().find(".player-profile").length).toBe(1)

Similarly you can access the properties and test it.

同样,您可以访问属性并对其进行测试。

回答by Qusai Jouda

You can use 'babel-plugin-remove-decorators' plugin. This solution will let you write your components normally without exporting decorated and un-decorated components.

您可以使用 'babel-plugin-remove-decorators' 插件。此解决方案将使您可以正常编写组件,而无需导出装饰和未装饰的组件。

Install the plugin first, then create a file with the following content, let us call it 'babelTestingHook.js'

先安装插件,然后创建一个包含以下内容的文件,我们称之为'babelTestingHook.js'

require('babel/register')({
 'stage': 2,
 'optional': [
  'es7.classProperties',
  'es7.decorators',
  // or Whatever configs you have
  .....
],
'plugins': ['babel-plugin-remove-decorators:before']
});

and running your tests like below will ignore the decorators and you will be able to test the components normally

运行如下测试将忽略装饰器,您将能够正常测试组件

mocha ./tests/**/*.spec.js --require ./babelTestingHook.js --recursive

回答by dtothefp

I think the above example is confusing because the decoratorconcept is used interchangeably with idea of a "higher order component". I generally use them in combination which will make testing/rewire/mocking easier.

我认为上面的例子令人困惑,因为这个decorator概念与“高阶组件”的概念可以互换使用。我通常将它们结合使用,这将使测试/重新布线/模拟更容易。

I would use decorator to:

我会使用装饰器来:

  • Provide props to a child component, generally to bind/listen to a flux store
  • 为子组件提供道具,通常用于绑定/监听通量存储

Where as I would use a higher order component

我会在哪里使用高阶组件

  • to bind context in a more declarative way
  • 以更具声明性的方式绑定上下文

The problem with rewiring is I don't think you can rewire anything that is applied outside of the exported function/class, which is the case for a decorator.

重新连接的问题是我认为您不能重新连接在导出的函数/类之外应用的任何东西,装饰器就是这种情况。

If you wanted to use a combo of decorators and higher order components you could do something like the following:

如果您想使用装饰器和高阶组件的组合,您可以执行以下操作:

//withMui-decorator.jsx
function withMUI(ComposedComponent) {
  return class withMUI extends Component {
    constructor(props) {
      super(props);
      this.state = {
        store1: ///bind here based on some getter
      };
    }
    render() {
      return <ComposedComponent {...this.props} {...this.state} {...this.context} />;
    }
  };
}

//higher-order.jsx
export default function(ChildComp) {

  @withMui  //provide store bindings
  return class HOC extends Component {
    static childContextTypes = {
      getAvatar: PropTypes.func
    };

    getChildContext() {
      let {store1} = this.props;

      return {
        getAvatar: (id) => ({ avatar: store1[id] });
      };
    }
  }
}

//child.js
export default Child extends Component {
  static contextTypes = {
    getAvatar: PropTypes.func.isRequired
  };
  handleClick(id, e) {
    let {getAvatar} = this.context;

    getAvatar(`user_${id}`);
  }
  render() {
    let buttons = [1,2,3].map((id) => {
      return <button type="text" onClick={this.handleClick.bind(this, id)}>Click Me</button>
    });

    return <div>{buttons}</div>;  
  }
}

//index.jsx
import HOC from './higher-order';
import Child from './child';

let MyComponent = HOC(Child);
React.render(<MyComponent {...anyProps} />, document.body);

Then when you want to test you can easily "rewire" your stores supplied from the decorator because the decorator is inside of the exported higher order component;

然后,当您想要测试时,您可以轻松地“重新连接”装饰器提供的商店,因为装饰器位于导出的高阶组件内部;

//spec.js
import HOC from 'higher-order-component';
import Child from 'child';

describe('rewire the state', () => {
  let mockedMuiDecorator = function withMUI(ComposedComponent) {
    return class withMUI extends Component {
      constructor(props) {
        super(props);
        this.state = {
          store1: ///mock that state here to be passed as props
        };
      }
      render()  {
        //....
      }
    }
  }

  HOC.__Rewire__('withMui', mockedMuiDecorator);
  let MyComponent = HOC(Child);

  let child = TestUtils.renderIntoDocument(
    <MyComponent {...mockedProps} />
  );

  let childElem = React.findDOMNode(child);
  let buttons = childElem.querySelectorAll('button');

  it('Should render 3 buttons', () => {
    expect(buttons.length).to.equal(3);
   });

});

I'm pretty sure this doesn't really answer your original question but I think you are having problems reconciling when to use decorators vs.higher order components.

我很确定这并没有真正回答您最初的问题,但我认为您在协调何时使用装饰器与高阶组件时遇到了问题。

some good resources are here:

一些好的资源在这里:

回答by Marty

In my case decorators are very useful and I dont want to get rid of them (or return wrapped and unwrapped versions) im my application.

在我的情况下,装饰器非常有用,我不想在我的应用程序中摆脱它们(或返回包装和未包装的版本)。

The best way to do this in my opinion is to use the babel-plugin-remove-decorators(which can be used to remove them in tests) has Qusai says, but I wrote the pre-processor differently like below:

在我看来,最好的方法是使用babel-plugin-remove-decoratorsQusai 所说的(可用于在测试中删除它们),但我编写了不同的预处理器,如下所示:

'use strict';

var babel = require('babel-core');

module.exports = {
  process: function(src, filename) {
    // Ignore files other than .js, .es, .jsx or .es6
    if (!babel.canCompile(filename)) {
     return '';
    }

    if (filename.indexOf('node_modules') === -1) {
      return babel.transform(src, {
        filename: filename, 
        plugins: ['babel-plugin-remove-decorators:before']
      }).code;
    }

    return src;
  }
};

Take notice of the babel.transformcall that im passing the babel-plugin-remove-decorators:beforeelement as an array value, see: https://babeljs.io/docs/usage/options/

注意babel.transformbabel-plugin-remove-decorators:before元素作为数组值传递的调用,请参阅:https: //babeljs.io/docs/usage/options/

To hook this up with Jest (which is what I used), you can do it with settings like below in your package.json:

要将它与 Jest(这是我使用的)连接起来,您可以使用如下设置来完成package.json

"jest": {
  "rootDir": "./src",
  "scriptPreprocessor": "../preprocessor.js",
  "unmockedModulePathPatterns": [
    "fbjs",
    "react"
  ]
},

Where preprocessor.jsis the name of the preprocessor.

preprocessor.js预处理器的名称在哪里。