利用 Jest 和Enzyme构建 TDD 应用程序

第一章 浅层渲染(Shallow Renderer)

当为 React 写单元测试时,浅层渲染(Shallow Renderer) 会变得十分有用。浅层渲染使你可以渲染 “单层深度” 的组件,并且对组件的 render 方法的返回值进行断言,不用担心子组件的行为,组件并没有实例化或被渲染。浅渲染并不需要 DOM。

第一节 概述
1.1 简单的使用

要开始使用 Jest,你不需要安装任何东西; 它是 Create React App 的一部分。如果查看 package.json 文件,您将看到已经有一个脚本用于运行测试。

function MyComponent() {
  return (
    <div>
      <span className="heading">Title</span>
      <Subcomponent foo="bar" />
    </div>
  );
}
import ShallowRenderer from 'react-test-renderer/shallow';

// in your test:
const renderer = new ShallowRenderer();
renderer.render(<MyComponent />);
const result = renderer.getRenderOutput();

expect(result.type).toBe('div');
expect(result.props.children).toEqual([
  <span className="heading">Title</span>,
  <Subcomponent foo="bar" />
]);
1.2 API讲解
shallowRenderer.render()shallowRenderer.render() 和 ReactDOM.render()很像。但是它不需要 DOM 并且只渲染一层。这就意味着你可以测试与子组件行为隔离的组件。
shallowRenderer.getRenderOutput()在 shallowRenderer.render() 被调用后, 你可以调用 shallowRenderer.getRenderOutput() 来获取浅渲染的输出.

expect在编写测试时,通常需要检查值是否满足某些条件。Expect 可以让你访问许多“匹配器” ,让你验证不同的东西。

.toBe(value)使用。比较基本值或检查对象实例的引用标识。它调用 Object.is 来比较值,这在测试中甚至比 = = = 严格相等运算符更好。

当我们在jest中进行年使用的时候

const can = {
  name: 'pamplemousse',
  ounces: 12,
};

describe('the can', () => {
  test('has 12 ounces', () => {
    expect(can.ounces).toBe(12);
  });

  test('has a sophisticated name', () => {
    expect(can.name).toBe('pamplemousse');
  });
});

.toMatchSnapshot(propertyMatchers?, hint?)这可以确保某个值与最近的快照匹配。有关详细信息,请参阅快照测试指南。

使用 Jest toMatchSnapshot 假设,您可以测试组件的结构。将呈现组件,而 toMatchSnapshot 将从这个呈现中创建一个快照,并在每次测试运行时将其与实际组件进行比较:

1.3 向 SubHeader 组件传值

您可以通过向 SubHeader 组件传递(例如) title prop 来检查快照的工作方式。要做到这一点,创建一个新的测试场景,它应该呈现带有标题的 SubHeader。另外,将渲染器常量的创建移动到 describe 函数,这样它就可以被所有的测试场景使用:

import React from 'react';
import ShallowRenderer from 'react-test-renderer/shallow';
import SubHeader from './SubHeader';

describe('the <SubHeader /> component', () => {
+  const renderer = new ShallowRenderer();

  it('should render', () => {
-   const renderer = new ShallowRenderer();    
    renderer.render(<SubHeader />);
    const component = renderer.getRenderOutput();

    expect(component).toMatchSnapshot();
  });

+  it('should render with a dynamic title', () => {
+    renderer.render(<SubHeader title='Test Application' />);
+    const component = renderer.getRenderOutput();

+    expect(component).toMatchSnapshot();
+  });
});

这里我们看到他传递了title = “Test Application”

image-20200922112729674

当你再次更改的时候,他会返回

image-20200922113118466

image-20200922112905925

通过按 u 键,您可以更新快照来处理这个新的测试场景。

当你按下u后

image-20200922114220891

除了 title 之外,这个组件使用 goBack 和 openForm 作为道具,其中 openForm 道具的默认值为 false。

当 goBack 有一个值时,创建一个按钮将我们带回到前一页,而当 openForm 有一个值时,创建一个按钮允许我们进入下一页,这样我们就可以添加一个新的评论。您还需要将这两个新的测试场景添加到 src/components/Header/SubHeader.test.js 文件中:

您现在已经为 SubHeader 组件创建了两个以上的快照,这将导致总共四个快照。Jest 做的另一件事是向您展示测试覆盖了多少行代码。您的测试覆盖率越高,就越有理由认为您的代码是稳定的。你可以通过使用 – coverage 标志执行测试脚本命令来检查你的代码的测试覆盖率,或者在你的终端中使用以下命令:

在创建后面两个快照的时候,会导致一些的错误

使用

 npm run test -- --coverage

来查看覆盖率

第二章 使用断言来测试组件

快照测试并不一定是不好的实践; 但是,随着时间的推移,您的文件可能会变得相当大

使用断言来测试

你可以删除快照测试,因为你只想用断言来测试孩子:

import React from 'react';
import ShallowRenderer from 'react-test-renderer/shallow';
import Button from './Button';

describe('the <Button /> component', () => {
    const renderer = new ShallowRenderer();
    

      it('should render the correct children', () => {
            const children = 'This is a button';
            renderer.render(<Button>{children}</Button>);
            const component = renderer.getRenderOutput();

            expect(component.props.children).toEqual(children);
          });
});

断言就是expect

第三章 使用Enzyme进行浅层渲染

npm install enzyme enzyme-adapter-react-16 --save-dev

Enzyme 和浅层渲染的区别

        -    renderer.render(<SubHeader title='Test Application' />);
        -    const component = renderer.getRenderOutput();
            const component = shallow(<SubHeader title='Test Application' />);

        -    renderer.render(<SubHeader goBack={() => {}} />);
        -    const component = renderer.getRenderOutput();
            const component = shallow(<SubHeader goBack={() => {}} />);
-   expect(component.props.children).toEqual(children)
+   expect(component.props().children).toEqual(children)

第四章 测试shallow rendering呈现的断言

一个简单的style的教程

https://www.jianshu.com/p/2d5f037c7df9

第一节 模拟点击事件
.simulate(event[, data]) => ShallowWrapper
Simulates an event on the current node.
expect(mockOnClick).toHaveBeenCalled();

使用.toHaveBeenCalled可以确保调用了模拟函数。

这样子 ,我们在button组件中设置好了,如何在

subHeader中使用

1.检查标题

    it('should render', ()=>{
        const component = shallow(<SubHeader />);
        expect(component).toMatchSnapshot();

    })

2.检查goBackButton 是不是存在

需要导入SubHeaderButton

    it('should render with a goback button', () => {
            const mockGoBack = jest.fn();
            const component = shallow(<SubHeader goBack={mockGoBack} />);

            const goBackButton = component.find(SubHeaderButton);
            expect(goBackButton.exists()).toBe(true);
        goBackButton.simulate('click');
        expect(mockGoBack).toHaveBeenCalled();
    });

SubHeaderButton 是使用style的,所以,你要先了解下

find(selector) => ShallowWrapper  

Find every node in the render tree that matches the provided selector.
.at(index) => ShallowWrapper
Returns a wrapper of the node at the provided index of the current wrapper.

完整的

it('should render with a buttons and handle the onClick events', () => {
 const mockGoBack = jest.fn();
     const mockOpenForm = jest.fn();
         const component = shallow(<SubHeader openForm={mockOpenForm} goBack={mockGoBack}  />);

         const goBackButton = component.find(SubHeaderButton).at(0);
    expect(goBackButton.exists()).toBe(true);

       const openFormButton = component.find(SubHeaderButton).at(1);
       expect(openFormButton.exists()).toBe(true)

    goBackButton.simulate('click');
    expect(mockGoBack).toHaveBeenCalled();

        openFormButton.simulate('click');
        expect(mockOpenForm).toHaveBeenCalled();
});

其实这里的测试有个问题

image-20200922165432918

当你调换openForm何goBack两者的位置,它没有因为洽谈而区别,因为他们有这一样的测试特征

第五章 与Enzyme的集成测试

第一节 测试Context

测试场景检查 Hotels 组件在第一次挂载时是否会从 Context 调用 getHotelsRequest 函数。这意味着在酒店使用的 useEffect Hook 已经被测试过了。

这里测试

import React from 'react';
import { mount } from 'enzyme';
import Hotels from './Hotels';

 let useContextMock;

 beforeEach(() => {
      useContextMock = React.useContext = jest.fn();
     });

 afterEach(() => {
      useContextMock.mockReset();
     });

describe('the <Hotels /> component', () => {
    it('should handle the first mount', () => {
            const mockContext = {
                  loading: true,
                  error: '',
                  hotels: [],
                  getHotelsRequest: jest.fn(),
                }
            useContextMock.mockReturnValue(mockContext);
            const wrapper = mount(<Hotels />);

                expect(mockContext.getHotelsRequest).toHaveBeenCalled();
    });
});

第二节 测试alert 加载数据

因为数据仍然在这里加载,所以我们还可以测试 Alert 组件是否正在呈现来自 Context 的加载值并显示加载消息。

- const Alert = styled.span`
+ export const Alert = styled.span`
- import Hotels from './Hotels';
+ import Hotels, { Alert } from './Hotels';
+   expect(wrapper.find(Alert).text()).toBe('Loading...');

在挂载了 Hotels 组件并获取了数据之后,上下文中的加载、错误和 hotel 的值将被更新。当加载和错误的值为 false 时,HotelItemsWrapper 组件将由 Hotels 呈现。

- const HotelItemsWrapper = styled.div`
+ export const HotelItemsWrapper = styled.div`
- import Hotels, { Alert } from './Hotels';
+ import Hotels, { Alert, HotelItemsWrapper } from './Hotels';
+  it('should render the list of hotels', () => {
+    const mockContext = {
+      loading: false,
+      error: '',
+      hotels: [{
+        id: 123,
+        title: 'Test Hotel',
+        thumbnail: 'test.jpg',
+      }],
+      getHotelsRequest: jest.fn(),
+    }
+    useContextMock.mockReturnValue(mockContext);
+    const wrapper = mount(<Hotels />);

+    expect(wrapper.find(HotelItemsWrapper).exists()).toBe(true);
+  });

会不会加载消息,使用text()

同时在理,我们还需要解决ROuter 的问题

你不应该在一个 < router > 外面使用 < Link > ,因为 Enzyme 不能呈现 Link 组件

+ import { BrowserRouter as Router } from 'react-router-dom';
-    const wrapper = mount(<Hotels />);
+    const wrapper = mount(<Router><Hotels /></Router>);

第三节 Context 的酒店数据

在 HotelItemsWrapper 组件内部有一个 map 函数,该函数迭代来自 Context 的酒店数据。

- const Title = styled.h3`
+ export const Title = styled.h3
+ import HotelItem, { Title } from './HotelItem';

+   expect(wrapper.find(HotelItem).exists()).toBe(true);
+ expect(wrapper.find(HotelItem).at(0).find(Title).text()).toBe(mockContext.hotels[0].title);

最后运行

npm run test --coverage!!

第六章 总结

当我们打开React官网的时候

他将测试分成了一下几种

1.创建/清理

我们已经用到过,在context的时候

常见的方法是使用一对 beforeEachafterEach 块,以便它们一直运行,并隔离测试本身造成的影响

2.act()

在编写 UI 测试时,可以将渲染、用户事件或数据获取等任务视为与用户界面交互的“单元”。react-dom/test-utils 提供了一个名为 act() 的 helper,它确保在进行任何断言之前,与这些“单元”相关的所有更新都已处理并应用于 DOM:

3.渲染

通常,你可能希望测试组件对于给定的 prop 渲染是否正确。此时应考虑实现基于 prop 渲染消

4.数据获取

你可以使用假数据来 mock 请求,而不是在所有测试中调用真正的 API。使用“假”数据 mock 数据获取可以防止由于后端不可用而导致的测试不稳定,并使它们运行得更快。注意:你可能仍然希望使用一个“端到端”的框架来运行测试子集,该框架可显示整个应用程序是否一起工作。

我们也已经用到过了

5.mock 模块

6.Events

我们建议在 DOM 元素上触发真正的 DOM 事件,然后对结果进行断言。

7.计时器

你的代码可能会使用基于计时器的函数(如 setTimeout)来安排将来更多的工作。

8.快照测试

像 Jest 这样的框架还允许你使用 toMatchSnapshot / toMatchInlineSnapshot 保存数据的“快照”。有了这些,我们可以“保存”渲染的组件输出,并确保对它的更新作为对快照的更新显式提交。

9.多渲染器

在极少数情况下,你可能正在使用多个渲染器的组件上运行测试。例如,你可能正在使用 react-test-renderer 组件上运行快照测试,该组件内部使用子组件内部的 ReactDOM.render 渲染一些内容。在这个场景中,你可以使用与它们的渲染器相对应的 act() 来包装更新。

显然,使用enzyme ,并遵行这些测试.

附录 参考

https://github.com/PacktPublishing/React-Projects/tree/master/Chapter06

https://www.packtpub.com/product/react-projects/9781789954937

接下去,我需要好好了解下,

进阶

https://www.packtpub.com/product/mastering-react-test-driven-development/9781789133417

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值