使用 Enzyme 进行 React 组件测试进阶

栏目: IOS · Android · 发布时间: 4年前

内容简介:很早之前,写过一篇项目使用安装 karma

很早之前,写过一篇 《使用enzyme 测试你的 React 组件》 , 综合叙述了如何利用 Karma + Webpack + Enzyme 进行组件的测试, 从而确保我们的质量。 相对而言,这次会丰富一些,比如组件的 UI 事件以及 redux 引入后的测试。

项目使用 yarn-react-webpack-seed 为案例,你可以在项目里找到源码。

建立测试

安装 karma

$ npm install karma karma-chai karma-chrome-launcher karma-coverage karma-jasmine karma-sourcemap-loader jasmine-core karma-webpack --save-dev

安装 enzyme 相关

npm install enzyme redux-mock-store enzyme-adapter-react-16 jasmine-enzyme --save-dev

在项目建立 test 目录,新增 karma.conf.js

然后在 package.json 的 script 添加

"test": "NODE_ENV=test karma start ./test/karma.conf.js --log-level debug"

接下里我们在 test 添加 specs 目录,用于存放我们的测试文件。

同时我们需要 test 添加 enzyme.js

import Enzyme from 'enzyme';  
import Adapter from 'enzyme-adapter-react-15.4';  
import jasmineEnzyme from 'jasmine-enzyme';

// Configure Enzyme for the appropriate React adapter
Enzyme.configure({ adapter: new Adapter() });

// Initialize global helpers
beforeEach(() => {  
  jasmineEnzyme();
});

// Re-export all enzyme exports
export * from 'enzyme';

接下来我们测试你一个简单的组件。

我们在 src/components 建立一个简单的 links.js 组件

import React, { Component } from 'react';  
import { Link } from 'react-router-dom';

class Links extends Component {

  render() {
    return (
      <div>
        <aside>
          <h4>Categories</h4>
          <ul>
            <li><Link to="/start">Start</Link></li>
            <li><Link to="/how">How</Link></li>
            <li><Link to="/guide">Guide</Link></li>
          </ul>
        </aside>
      </div>
    );
  }
}


export default Links;

src/components/links.js

我们 test/specs 目录最好也还是和 src 目录保持,只是测试的文件添加 .test.js 来表示它是用于测试的文件。

import React from 'react';  
import Links from '../../src/components/links';  
import { shallow } from '../../enzyme';


describe('Links component test', () => {

  it('render links', () => {
    const wrapper = shallow(
      <Links />
    );
    expect(wrapper.find('li').length).toBe(3);
  });

})

大概就是确定我们渲染后的节点,内容,关于 wrapper 支持的 API 。可以参考 Rendering API

模拟交互事件

当然,如果我们组建比较复杂不单单只是一些属性的判断而还有一些事件的交互,比如点击,输入框内容变化,等等。

我们可以在 src/components 下 建立一个 form.js

import React, { Component } from 'react';

class Form extends Component {  
  constructor(props) {
    super(props);
    this.state = {
      phone: 123,
      error: false,
    };
    this.onChange = this.onChange.bind(this);
    this.reset = this.reset.bind(this);
  }

  onChange(e) {
    const val = e.target.value;
    if (!/^[1][3,4,5,7,8][0-9]{9}$/.test(val)) {
      this.setState({
        error: true,
      });
    } else {
      this.setState({
        phone: val,
        error: false,
      });
    }
  }

  reset() {
    this.setState({
      phone: '',
      error: false,
    });
  }

  render() {
    const { phone, error } = this.state;
    return (
      <div className="m-form">
        <h3>This is form</h3>
        <p>
          <span className="js-contents">{phone}</span>
        </p>
        <p>
          <input type="text" onChange={this.onChange} />
        </p>
        {
          error && (<div className="js-error">Error Phone Number</div>)
        }
        <p><button onClikc={this.reset} type="button">Reset</button></p>
      </div>
    );
  }
}

export default Form;

一个简单的输入手机号并验证的情况。

enzyme 可以通过 simulate 来模拟 DOM 的交互事件,比如 click , focus , blur 等事件。

我们模拟用户输入手机可以这么写了

import React from 'react';  
import Form from '../../../src/components/form';  
import { mount } from '../../enzyme';


describe('Form component test', () => {

  it('render links', () => {
    const wrapper = mount(
      <Form />
    );
    expect(wrapper.find('input').length).toBe(1);
    expect(wrapper.find('button').length).toBe(1);
  });
})

我们通过 wrapper 先找到对应的输入框,这样我们可以来模拟输入值得变化。

判断函数调用

接触过单元测试的话,都知道 spy 。我们同理在 React 的测试用到 spy 来确定按钮点击等触发的函数调用。

it('# reset prop', () => {  
    const foo = {
      count: 0,
      setBar() {
        this.count ++;
      }
    };

    spyOn(foo, 'setBar');
    const wrapper = mount(
      <Form
        reset={foo.setBar}
      />
    );
    wrapper.find('button').simulate('click');
    expect(foo.setBar).toHaveBeenCalled();
  });

如果你希望判断调用次数以及传入的参数可以查看 section-Spies 的文档。

Redux 引入

现在越来越多的 React 应用引入了 redux

比如我们设计一个简单的引入拉取数据的的程序,然后渲染列表。

具体 Demo Code

/* eslint-disable arrow-parens */
import React, { Component } from 'react';  
import { connect } from 'react-redux';  
import { fetchList, fetchTags } from '../actions/guide';


class Guide extends Component {

  componentDidMount() {
    this.fetchList();
  }

  fetchList() {
    const { dispatch } = this.props;
    dispatch(fetchList());
    dispatch(fetchTags());
  }

  render() {
    const { data, tags} = this.props;
    let movies = null;
    let tagCom = null;
    if (tags && tags.length > 0 ) {
      tagCom = tags.map((item) => {
        return (
          <a href="#">{ item }</a>
        );
      });
    }
    if (data && data.length > 0 ) {
      movies = data.map((item) => {
        return (
          <div>
            <h3>{item.name}</h3>
            <p> :sparkles: {item.rank}</p>
          </div>
        );
      });
    }
    return (
      <div>
        <h3>This is guide</h3>
        <h4>Redux Test</h4>
        <p className="tag-list">{ tagCom }</p>
        { movies }
      </div>
    );
  }
}

const mapStateToProps = (state) => ({  
  data: state['guide']['data'],
  tags: state['guide']['tags'],
});

export default connect(mapStateToProps)(Guide);

如果我们需要测试引入 store 的组件,我们需要先安装 redux-mock-store

npm install redux-mock-store --save-dev

接下来,我们先写模拟数据状态的文件,新建 test/fixtures/state.js

const mockState = {  
  guide: {
    data: [
      {
        name: 'KungFu Hustle',
        rank: '8.3',
      },
      {
        name: 'CJ-7',
        rank: '6.4',
      },
    ],
    tags: ['movie', 'comedy'],
  },
};

export default mockState;

编写我们的测试文件 guide.test.js

import React from 'react';  
import configureStore from 'redux-mock-store'  
import { Provider } from 'react-redux';  
import Guide from '../../../src/components/guide';  
import { mount } from '../../enzyme';  
import mockState from '../fixtures/state';


const mockStore = configureStore();  
let wrapper;  
let store;  
beforeEach(() => {  
  // 创建关联 store
  store = mockStore(mockState);
  // 渲染测试的组件将 store 传入
  wrapper = mount(<Provider store={store}><Guide /></Provider>)
});
describe('Links component test', () => {  
  it('render movies and tags', () => {
    expect(wrapper.find('.movie-item').length).toBe(2);
    expect(wrapper.find('.tag-item').length).toBe(2);
  });
});

这样就可以针对引入 redux 的组件进行测试。

最后你可以在 yarn-react-webpack-seed 找到相关演示代码。

小结

本文在原有测试基础上,增加了对组件的事件交互和数据交互的测试,对于前端而言无论是 TDD 还是 BDD, 尝试去写自己组件的测试用例都有助于工程质量的提升和本身产品的效能开发,不过写好测试依旧需要花些时间。

扩展阅读


以上所述就是小编给大家介绍的《使用 Enzyme 进行 React 组件测试进阶》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Rust编程之道

Rust编程之道

张汉东 / 电子工业出版社 / 2019-1 / 128

Rust 是一门利用现代化的类型系统,有机地融合了内存管理、所有权语义和混合编程范式的编程语言。它不仅能科学地保证程序的正确性,还能保证内存安全和线程安全。同时,还有能与C/C++语言媲美的性能,以及能和动态语言媲美的开发效率。 《Rust编程之道》并非对语法内容进行简单罗列讲解,而是从四个维度深入全面且通透地介绍了Rust 语言。从设计哲学出发,探索Rust 语言的内在一致性;从源码分析入......一起来看看 《Rust编程之道》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

随机密码生成器
随机密码生成器

多种字符组合密码