Material UI + Enzyme 测试组件

Posted

技术标签:

【中文标题】Material UI + Enzyme 测试组件【英文标题】:Material UI + Enzyme testing component 【发布时间】:2018-11-27 16:58:35 【问题描述】:

我在 React 中有一个组件,我正在尝试用 Jest 进行测试,不幸的是测试没有通过。

组件代码:

import React, Component from 'react';
import ProductItem from '../ProductItem/ProductItem';
import AppBar from "@material-ui/core/es/AppBar/AppBar";
import Tabs from "@material-ui/core/es/Tabs/Tabs";
import Tab from "@material-ui/core/es/Tab/Tab";
import connect from 'react-redux';


class ProductsTabsWidget extends Component 

    state = 
        value: 0
    

    renderTabs = () => 
        return this.props.tabs.map((item, index) => 
            return item.products.length > 0 ? (<Tab key=index label=item.title/>) : false;
        )
    

    handleChange = (event, value) => 
        this.setState(value);
    ;


    renderConentActiveTab = () => 
        if (this.props.tabs[this.state.value]) 
            return this.props.tabs[this.state.value].products.map((productIndex) => 
                return (<ProductItem key=productIndex ...this.props.products[productIndex] />);
            );
        
    

    render() 
        let tabs = null;
        let content = null;
        if (this.props.tabs) 
            tabs = this.renderTabs();
            content = this.renderConentActiveTab();
        
        return (
            <div>
                <AppBar position="static" color="default">
                    <Tabs
                        value=this.state.value
                        onChange=this.handleChange
                        indicatorColor="primary"
                        textColor="primary"
                        centered
                        scrollButtons="auto"
                    >
                        tabs
                    </Tabs>
                </AppBar>
                <div className="productWidget">
                    <div className="wrapper">
                        content
                    </div>
                </div>
            </div>
        )
    


const mapStateToProps = state => 
    return 
        products: state.product.products,
    


export default connect(mapStateToProps)(ProductsTabsWidget);

我已经尝试为这个组件编写适当的测试,代码如下:

import React from 'react';

import configure, shallow from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import ProductsTabsWidget from "./ProductsTabsWidget";


configure(adapter: new Adapter());

describe('ProductsTabsWidget - component', () => 
    let wrapper;


    beforeEach(() => 
        wrapper = shallow(<ProductsTabsWidget/>);
    );

    it('renders with minimum props without exploding', () => 
        wrapper.setProps(
            tabs: [],
            products:[]
        );
        expect(wrapper).toHaveLength(1);
    );
)

但是当我运行测试时出现错误:

Test suite failed to run

    F:\PRACA\reactiveShop\node_modules\@material-ui\core\es\AppBar\AppBar.js:1
    ("Object.<anonymous>":function(module,exports,require,__dirname,__filename,global,jest)import _extends from "@babel/runtime/helpers/builtin/extends";
                                                                                             ^^^^^^

    SyntaxError: Unexpected token import

      at new Script (vm.js:51:7)
      at Object.<anonymous> (src/components/product/ProductsTabsWidget/ProductsTabsWidget.js:3:15)

我曾尝试使用shallowmountrender 进行测试,但没有帮助。我错过了什么?

我的应用程序是在 create-react-app 上创建的。

【问题讨论】:

SyntaxError: Unexpected token import 似乎是 Babel 配置错误,它不应该出现在基本的 CRA 设置中,也许你 ejected 它的一些功能?一般来说,这个问题似乎与配置jest: Test suite failed to run, SyntaxError: Unexpected token import有关。 【参考方案1】:

当您使用 @material-ui 时会有所不同。

您必须使用 @material-ui 的内置 API。如createMount,createShallow,createRender为了使用酶的shallow,mount&render

这些 API 构建在 enzyme 之上,因此您不能直接使用 enzyme 来测试 @material-ui


Shallow 使用 @material-ui 渲染的示例

import  createShallow  from '@material-ui/core/test-utils';

describe('<MyComponent />', () => 
  let shallow;

  before(() => 
    shallow = createShallow(); 
  );

  it('should work', () => 
    const wrapper = shallow(<MyComponent />);
  );
);

参考:Official Docs of @material-ui

【讨论】:

我知道这有点“旧”的帖子,但我不认为这是完全正确的。我在一个项目(称为 mystyle)中非常使用通过我自己的 npm 包公开的材料 UI,我在其中设置了我的组件的样式。然后将该包导入另一个项目(myapp),我在其中执行测试(即,我使用自己的组件和引擎盖下的材质 UI)。 myapp 中的测试是使用酶和玩笑编写的。他们工作得很好。 Material-UI 正在弃用他们的测试工具,并且将不再出现在 v5 中。【参考方案2】:

以下是从create-react-app@material-ui 的角度提供更完整答案的谦虚尝试。

1. 直接在src 文件夹中创建setupTests.js 并粘贴以下代码。

import  configure  from "enzyme";
import Adapter from "enzyme-adapter-react-16";

configure( adapter: new Adapter() );

2.以下是使用material-ui组件的react无状态组件。

import React from "react";
import TextField from "@material-ui/core/TextField";

const SearchField = props => (
   <TextField InputProps= disableUnderline: true  fullWidth
              placeholder=props.placeholder
              onChange=props.onChange
   />
);

export default SearchField;

请注意,在上面的组件中,组件期望父组件传递 placeholderonChange() 事件处理程序的 props

3. 进入上述组件的测试用例,我们可以用material-ui 建议的方式或pure enzyme 样式编写。两者都可以。

Pure Enzyme风格

import React from "react";
import  mount  from "enzyme";
import TextField from "@material-ui/core/TextField";
import SearchField from "../SearchField";
describe("SearchField Enzyme mount() ", () => 
  const fieldProps = 
    placeholder: "A placeholder",
    onChange: jest.fn()
  ;
  const Composition = props => 
    return <SearchField ...fieldProps />;
  ;

  it("renders a <TextField/> component with expected props", () => 
    const wrapper = mount(<Composition />);
    expect(wrapper.childAt(0).props().placeholder).toEqual("A placeholder");
    expect(wrapper.childAt(0).props().onChange).toBeDefined();
  );
  it("should trigger onChange on <SearchField/> on key press", () => 
    const wrapper = mount(<Composition />);
    wrapper.find("input").simulate("change");
    expect(fieldProps.onChange).toHaveBeenCalled();
  );
  it("should render <TextField />", () => 
    const wrapper = mount(<Composition />);
    expect(wrapper.find(TextField)).toHaveLength(1);
    expect(wrapper.find(TextField).props().InputProps.disableUnderline).toBe(
      true
    );
  );
);

Material UI 风格

import React from "react";
import  createMount  from "@material-ui/core/test-utils";
import TextField from "@material-ui/core/TextField";
import SearchField from "../SearchField";
describe("SearchField", () => 
  let mount;
  const fieldProps = 
    placeholder: "A placeholder",
    onChange: jest.fn()
  ;
  beforeEach(() => 
    mount = createMount();
  );

  afterEach(() => 
    mount.cleanUp();
  );

  it("renders a <TextField/> component with expected props", () => 
    const wrapper = mount(<SearchField ...fieldProps />);
    expect(wrapper.props().placeholder).toEqual("A placeholder");
    expect(wrapper.props().onChange).toBeDefined();
  );
  it("should trigger onChange on <SearchField/> on key press", () => 
    const wrapper = mount(<SearchField ...fieldProps />);
    wrapper.find("input").simulate("change");
    expect(fieldProps.onChange).toHaveBeenCalled();
  );
);

5. 你得到的错误是由于babel 没有机会处理你的文件。 create-react-app 期望您像 yarn run test 那样运行测试,而不是像 jest your/test/file.js 那样运行测试。如果你使用后者babel 将不会是employed

如果您想使用jest 运行该文件,您必须编写一个jest.config.js 文件或在package.json 文件中配置jest 以使用babel-jest + other babel dependencies 在开玩笑之前转译您的代码尝试执行测试。

昨天我第一次尝试使用@material-ui时,我在同一条船上,来到这里以获得更完整的答案。

【讨论】:

那么在您的情况下,您使用什么命令来运行测试? npm testyarn test?【参考方案3】:

这样的事情对我有用:

import createMount from '@material-ui/core/test-utils';

const WrappedComponent = () => 
    <MUIThemeStuffEtc>
        <MyComponent />
    </MUIThemeStuffEtc>

const render = createMount();
const wrapper = render(<WrappedComponent />);

const state = wrapper.find(MyComponent).instance().wrappedInstance.state

【讨论】:

以上是关于Material UI + Enzyme 测试组件的主要内容,如果未能解决你的问题,请参考以下文章

酶不模拟 React Material-UI v1 上的更改事件 - 选择组件

对 Material UI Select 组件的更改做出反应测试库

如何使用 react-testing-library 测试包装在 withStyles 中的样式化 Material-UI 组件?

用玩笑模拟material-ui选择组件

Jest + Enzyme React 组件测试实践

Jest + Enzyme React 组件测试实践