• 日常搜索
  • 百度一下
  • Google
  • 在线工具
  • 搜转载

使用Jest 和Enzyme测试React中的组件

这是在 react 中测试组件系列的第二部分。如果您之前有使用 Jest 的经验,则可以跳过并使用 GitHub 代码作为起点。 

在 上一篇文章中,我们介绍了测试驱动开发背后的基本原则和思想。我们还设置了在 React 中运行测试所需的环境和工具。该工具集包括 Jest、ReactTestUtils、Enzyme 和 react-test-renderer。 

使用Jest 和Enzyme测试React中的组件  第1张React 使用 Jest 在 React 中测试组件:基础知识 Manjunath M

然后,我们使用 ReactTestUtils 为演示应用程序编写了几个测试,并发现与像 Enzyme 这样更健壮的库相比,它的缺点。

在这篇文章中,我们将通过编写更实际和现实的测试来更深入地了解 React 中的测试组件。在开始之前,您可以前往 GitHub 并克隆我的存储库

开始使用 Enzyme api

Enzyme.js 是一个由 Airbnb 维护的开源库,它是 React 开发人员的绝佳资源。它使用底层的 ReactTestUtils API,但与 ReactTestUtils 不同的是,Enzyme 提供了高级 API 和易于理解的语法。如果你还没有安装 Enzyme。

Enzyme API 导出三种类型的渲染选项:

  1. 浅渲染

  2. 渲染

  3. 静态渲染

浅渲染用于单独渲染特定组件。子组件不会被渲染,因此您将无法断言它们的行为。如果你打算专注于单元测试,你会喜欢的。您可以像这样浅渲染组件:

import { shallow }  from 'enzyme';
import ProductHeader from './ProductHeader';

// More concrete example below.
 const component = shallow(<ProductHeader/>);

dom 渲染在名为 jsdom 的库的帮助下生成组件的虚拟 DOM。shallow()您可以通过将方法替换mount()为上述示例中的方法来利用此功能 。明显的好处是您也可以渲染子组件。如果你想测试一个组件及其子组件的行为,你应该使用它。 

静态渲染用于将 react 组件渲染为静态 html它是使用一个名为 Cheerio 的库来实现的,您可以在docs中阅读有关它的更多信息。 

重温我们以前的测试

以下是我们在上一个教程中编写的测试:

src/components/__tests__/ProductHeader.test.js

import ReactTestUtils from 'react-dom/test-utils'; // ES6

describe('ProductHeader Component', () => {
    it('has an h2 tag', () => {

      const component = ReactTestUtils
                            .renderIntoDocument(<ProductHeader/>);    
      var node = ReactTestUtils
                    .findRenderedDOMComponentWithTag(
                     component, 'h2'
                    );
    
  });

    it('has a title class', () => {

      const component = ReactTestUtils
                            .renderIntoDocument(<ProductHeader/>);    
      var node = ReactTestUtils
                    .findRenderedDOMComponentWithClass(
                     component, 'title'
                 );
    })
  })

第一个测试检查ProducerHeader组件是否有<h2>标签,第二个测试检查它是否有一个名为 的csstitle代码很难阅读和理解。 

以下是使用 Enzyme 重写的测试。

src/components/__tests__/ProductHeader.test.js

import { shallow } from 'enzyme'

describe('ProductHeader Component', () => {

    it('has an h2 tag', () => {
      const component = shallow(<ProductHeader/>);    
      var node = component.find('h2');
      expect(node.length).toEqual(1);
     
  });

    it('has a title class', () => {
      const component = shallow(<ProductHeader/>);
      var node = component.find('h2');
      expect(node.hasClass('title')).toBeTruthy();
    })
  })

首先,我使用创建了 <ProductHeader/> 组件的浅渲染 DOMshallow()并将其存储在一个变量中。然后,我用这个.find()方法找到了一个带有标签'h2'的节点。它查询 DOM 以查看是否存在匹配项。由于节点只有一个实例,我们可以安全地假设它node.length等于 1。

第二个测试与第一个非常相似。hasClass('title')方法返回当前节点是否className有值为'title'的道具。我们可以使用 来验证真实性 toBeTruthy()。  

使用 运行测试yarn test,两个测试都应该通过。 

做得好!现在是重构代码的时候了。从测试人员的角度来看,这很重要,因为可读的测试更容易维护。在上述测试中,前两行对于两个测试都是相同的。您可以使用beforeEach()函数重构它们。顾名思义,该beforeEach函数在执行描述块中的每个规范之前被调用一次。 

你可以传递一个箭头函数来beforeEach()喜欢这个。

src/components/__tests__/ProductHeader.test.js

import { shallow } from 'enzyme'

describe('ProductHeader Component', () => {
    let component, node;
    
    // Jest beforeEach()
    beforeEach((()=> component = shallow(<ProductHeader/>) ))
    beforeEach((()=> node = component.find('h2')) )
    
    it('has an h2 tag', () => {
        expect(node).toBeTruthy()
    });

    it('has a title class', () => {
      expect(node.hasClass('title')).toBeTruthy()
    })
})

使用 Jest 和 Enzyme 编写单元测试

让我们为ProductDetails组件编写一些单元测试。它是一个展示组件,显示每个单独产品的详细信息。 

使用Jest 和Enzyme测试React中的组件  第2张我们将测试突出显示的部分

单元测试将尝试断言以下假设:

  • 该组件存在并且道具正在传递。

  • 显示产品名称、描述和可用性等道具。

  • 当 props 为空时会显示错误消息。

这是测试的基本结构。第一个beforeEach()将产品数据存储在一个变量中,第二个安装组件。

src/components/__tests__/ProductDetails.test.js

describe("ProductDetails component", () => {
    var component, product;

    beforeEach(()=> {
        product = {
            id: 1,
            name: 'NIKE Liteforce Blue Sneakers',
            description: 'Lorem ipsum.',
            status: 'Available'
        };
    })
    beforeEach(()=> {
        component = mount(<ProductDetails product={product} foo={10}/>);
    })

    it('test #1' ,() => {
     
    })
})

第一个测试很简单:

it('should exist' ,() => {
      expect(component).toBeTruthy();
      expect(component.props().product).toEqual(product);
 })

这里我们props()使用方便获取组件道具的方法。

对于第二个测试,您可以通过元素的类名查询元素,然后检查产品名称、描述等是否属于该元素的innerText

  it('should display product data when props are passed', ()=> {
       let title = component.find('.product-title');
       expect(title.text()).toEqual(product.name);
       
       let description = component.find('.product-description');
       expect(description.text()).toEqual(product.description);
       
    })

在这种情况下,该text()方法对于检索元素的内部文本特别有用。尝试为 写一个期望,product.status()看看是否所有的测试都通过了。

对于最终测试,我们将在ProductDetails没有任何道具的情况下安装组件。然后我们将寻找一个名为“.product-error”的类并检查它是否包含文本“对不起,产品不存在”。

 it('should display an error when props are not passed', ()=> {
        /* component without props */
        component = mount(<ProductDetails />);

        let node = component.find('.product-error');
        expect(node.text()).toEqual('Sorry. Product doesnt exist');
    })

而已。我们已经成功地单独测试了该<ProductDetails />组件。这种类型的测试称为单元测试。

使用存根和间谍测试回调

我们刚刚学会了如何测试道具。但是要真正孤立地测试一个组件,您还需要测试回调函数。在本节中,我们将为ProductList组件编写测试,并在此过程中为回调函数创建存根。这是我们需要断言的假设。

使用Jest 和Enzyme测试React中的组件  第3张

  1. 列出的产品数量应等于组件作为道具接收的对象数量。

  2. 点击<a>应该调用回调函数。

让我们创建一个 beforeEach() 为我们的测试填充模拟产品数据的函数。

src/components/__tests__/ProductList.test.js

  beforeEach( () => {
         productData =   [
            {
                id: 1,
                name: 'NIKE Liteforce Blue Sneakers',
                description: 'Lorem ipsu.',
                status: 'Available'
        
            },
           // Omitted for brevity
        ]
    })

现在,让我们将我们的组件安装在另一个beforeEach()块中。

beforeEach(()=> {
    handleProductClick = jest.fn();
    component = mount( 
                    <ProductList 
                        products = {productData} 
                        selectProduct={handleProductClick} 
                    />
                );
})

通过ProductListprops 接收产品数据。除此之外,它还会收到来自父级的回调。尽管您可以为父级的回调函数编写测试,但如果您的目标是坚持单元测试,那么这不是一个好主意。由于回调函数属于父组件,结合父组件的逻辑会使测试变得复杂。相反,我们将创建一个存根函数。

什么是存根? 

存根是一个虚拟函数,它伪装成其他函数。这允许您在不导入父组件或子组件的情况下独立测试组件。在上面的示例中,我们创建了一个handleProductClick通过 invoking 调用的存根函数 jest.fn()。 

现在我们只需要找到<a>DOM 中的所有元素并模拟单击第一个<a>节点。被点击后,我们会检查是否handleProductClick()被调用。如果是,可以公平地说我们的逻辑按预期工作。

it('should call selectProduct when clicked', () => {

    const firstLink = component.find('a').first();
    firstLink.simulate('click');
    expect(handleProductClick.mock.calls.length).toEqual(1);

    })
})

Enzyme 让您可以轻松地模拟用户操作,例如使用 simulate() 方法的点击。 handlerProductClick.mock.calls.length 返回调用模拟函数的次数。我们希望它等于 1。

另一个测试相对容易。您可以使用该find()方法检索<a>DOM 中的所有节点。节点的数量<a>应该等于我们之前创建的 productData 数组的长度。 

   it('should display all product items', () => {
    
        let links = component.find('a');
        expect(links.length).toEqual(productData.length);
    })

测试组件的状态、LifeCycleHook 和方法

接下来,我们将测试该ProductContainer 组件。它有一个状态、一个生命周期钩子和一个类方法。以下是需要验证的断言:

  1. componentDidMount只调用一次。

  2. 组件的状态在组件安装后填充。

  3. handleProductClick()当产品 id 作为参数传入时,该方法应该更新状态。

为了检查是否componentDidMount被调用,我们将对其进行监视。与存根不同,当您需要测试现有功能时使用间谍。一旦设置了 spy,您就可以编写断言来确认该函数是否被调用。

您可以按如下方式监视函数:

src/components/__tests__/ProductContainer.test.js

    it('should call componentDidMount once', () => {
        componentDidMountSpy = spyOn(ProductContainer.prototype, 
                               'componentDidMount');
        //To be finished
    });

第一个参数jest.spyOn一个对象,它定义了我们正在监视的类的原型。第二个是我们要监视的方法的名称。 

现在渲染组件并创建一个断言来检查是否调用了 spy。

     component = shallow(<ProductContainer/>);
     expect(componentDidMountSpy).toHaveBeenCalledTimes(1);

要在组件挂载后检查组件的状态是否已填充,我们可以使用 Enzyme 的state()方法来检索状态中的所有内容。 

it('should populate the state', () => {
        component = shallow(<ProductContainer/>);
        expect(component.state().productList.length)
            .toEqual(4)

    })

第三个有点棘手。我们需要验证它handleProductClick是否按预期工作。如果您查看代码,您会看到该handleProductClick()方法将产品 ID 作为输入,然后this.state.selectedProduct使用该产品的详细信息进行更新。 

为了测试这一点,我们需要调用组件的方法,实际上你可以通过调用 component.instance().handleProductClick()我们将传入一个示例产品 ID。在下面的示例中,我们使用第一个产品的 id。然后,我们可以测试状态是否已更新,以确认断言为真。这是整个代码:

 it('should have a working method called handleProductClick', () => {
        let firstProduct = productData[0].id;
        component = shallow(<ProductContainer/>);
        component.instance().handleProductClick(firstProduct);

        expect(component.state().selectedProduct)
            .toEqual(productData[0]);
    })

我们已经编写了 10 个测试,如果一切顺利,您应该看到以下内容:

使用Jest 和Enzyme测试React中的组件  第4张

概括

呸!我们已经涵盖了开始使用 Jest 和 Enzyme 在 React 中编写测试所需了解的几乎所有内容。现在可能是前往Enzyme 网站深入了解他们的 API 的好时机。



文章目录
  • 开始使用 Enzyme api
  • 重温我们以前的测试
      • src/components/__tests__/ProductHeader.test.js
      • src/components/__tests__/ProductHeader.test.js
      • src/components/__tests__/ProductHeader.test.js
  • 使用 Jest 和 Enzyme 编写单元测试
      • src/components/__tests__/ProductDetails.test.js
  • 使用存根和间谍测试回调
      • src/components/__tests__/ProductList.test.js
    • 什么是存根?
  • 测试组件的状态、LifeCycleHook 和方法
      • src/components/__tests__/ProductContainer.test.js
  • 概括