07-测试无状态函数组件
9.3.2 测试无状态函数组件
是时候开始编写一些测试了。首先,我们将关注一个相对简单的测试组件示例。我们将测试Content组件。它只是在其内部渲染了一个带有内容的段落,其他什么都没做。代码清单9-2展示了这个组件的结构。
代码清单9-2 Content组件(src/components/post/Content.test.js)
import React, { PropTypes } from 'react';
const Content = (props) => { ⇽--- 组件接收props对象并使用post的content属性来渲染段落元素
const { post } = props;
return (
<p className="content"> ⇽--- 将content类样式赋值给段落
{post.content} ⇽--- 段落元素的内容是来自post对象的content属性
</p>
);
};
Content.propTypes = {
post: PropTypes.object,
};
export default Content; ⇽--- 导出组件——这很重要,因为需要在测试中导入组件
在开始编写测试之前,首先要做的事情之一是考虑要验证哪些假设。也就是说,一旦所有测试通过,它们应该向使用者确认某些事情并作为一种保证。实际上,我对测试最喜欢的事情之一就是,当修改了特定功能或系统的一部分时,我依赖它们的失败。它们支持我的假设,即我所做的更改代表了对应用程序或系统的更改。这让我在编写代码时感觉更舒服,一方面因为我事先就有事情应该如何工作的记录,另一方面因为我可以全面了解更改如何影响应用程序。
让我们来看看这个组件,考虑如何测试它。关于这个组件,有一些想要验证的假设。其一,它需要呈现一些作为属性传入的内容,还需要给段落元素赋值类名;其二,组件需要关注的就没什么了。这些东西足以使你开始编写测试。
你可能注意到,“React正确运行”并非这里要测试的内容之一。我们还排除了诸如“函数可以被执行”“JSX转译器能运行”之类的东西,以及其他一些关于我们正在使用的技术的基本假设。这些东西确实重要到需要测试,但我们编写的测试永远无法充分或准确地验证这些假设。这些项目要负责编写自己的测试并确保自己能正常工作。这就强调了选择可靠、经过良好测试并保持更新的软件的重要性。如果你严重怀疑React的可靠性,你的怀疑可能是没有根据的。
纵然不完美,React仍然在一些全球最受欢迎的Web应用程序上得到了应用,包括Facebook和Netflix网站。虽然确实有bug,但在我们这种简单直接的情形中不太可能遇到它们。
你知道一些想要验证的组件的事情,但如果从头开始并且先编写测试的话,也可以用另一种方式。你可能想:“我们需要一个显示内容的组件,它具有特定类型并且具有特定的CSS类名——如此CSS才能起作用。”之后就可能编写验证这些条件的测试。由于一直学习React的缘故,你会先写代码再写测试,但我们可以看到从测试开始是如何让事情变得容易的:一开始就必须仔细考虑并规划组件。如前所述,测试驱动开发(TDD)是一种将先编写测试作为软件开发核心的流派。
让我们来看看如何测试这个组件。要做到这一点,需要编写一个测试套件——它是一组测试。单个测试通过断言(关于代码的声明,可以返回真或假)来验证假设。例如,组件的一个测试会断言设置了正确的类样式名。如果任何断言失败,那么这个测试就会失败。这就是得知应用中有些东西发生了不经意的改变或者不再工作的方法。代码清单9-3展示了如何建立测试骨架。
代码清单9-3 Content组件的测试骨架(src/components/post/Content.test.js)
import React from 'react'; ⇽--- 导入React
import { shallow } from 'enzyme'; ⇽--- 导入相关的辅助方法
import renderer from 'react-test-renderer';
import { Content } from './Content'; ⇽--- 导入被测组件
describe('<Content/>', () => { ⇽--- Jest使用诸如describe这样Jasmine风格的方法来组织测试
test('should render correctly', () => { ⇽--- 一个实际测试——it函数也是由jest全局提供的
});
});
注意,这个组件的测试文件以.test.js结尾。这是一个惯例,如果愿意可以选择遵循。Jest默认情况下会查找以.spec.js或者.test.js结尾的文件并运行这些测试。如果选择遵循不同的惯例,就需要将它们添加到命令行调用中(如 jest --watch ./my.cool.test.file.js
)来显式地告诉Jest要运行哪些文件。本书所有测试都将遵循.test.js惯例。
还要注意测试文件的放置位置。有些人选择把所有测试都放在一个叫作test的“镜像”目录中,该目录通常位于项目的根目录下。对于每一个要测试的文件,都会在测试目录中创建一个对应的文件。这是一种很好的文件组织方式,也可以将测试文件放置在它们的源文件旁边。我们将采用这种方法,但无论哪种方法都可以。
你也许已经注意到,到目前为止 describe
函数并没有什么特别之处,它们主要是用于组织并确保将测试分割为适当的块来测试代码的不同部分。对这样的小文件来说,似乎并没有多大必要这么做,但是我曾经处理过2000~3000行(甚至更多)的测试文件,依经验而言:可读性强的测试有助于做出好的测试。
编写简洁的测试
你是否读过没有与被测代码得到同等对待的测试代码?这种事我已经碰到过不止一次了。阅读不简洁的测试代码会让人感到困惑甚至沮丧。测试只是更多的代码而已,所以它们也需要简洁可读,对吧?我在本章已经提到过测试有时候被认为次于应用程序代码的编写。测试代码被视为不得不干的任务,甚至是开发者与应用程序代码之间的障碍,因此就会降低标准。很容易陷入这种趋势,然而实际上写得差的测试与写得差的应用程序代码一样糟糕。测试应该作为另一种形式的代码文档,开发人员仍然需要阅读它。记住,测试代码应该保持简洁。
Jest将查找要测试的文件,然后执行这些不同的 describe
和 it
函数,调用提供给它们的回调函数。但需要在回调函数中放什么呢?需要设立断言。要做到这一点,需要一些可以断言的东西。这就是Enzyme的用武之地,它允许创建组件的可测试版本——可以对其进行检视与断言。我们会使用Enzyme的浅渲染,它将创建组件的轻量级版本,其不会执行完全的挂载或向DOM中进行插入。我们还需要提供一些mock(假的)数据供组件使用。代码清单9-4展示了如何将组件的测试版本添加到测试套件中。开始编写测试之前,请确保在终端运行 npm run test:w
命令来启动测试运行器。
代码清单 9-4 浅渲染(src/components/post/Content.test.js)
import React from 'react';
import { shallow } from 'enzyme';
import renderer from 'react-test-renderer';
import { Content } from './Content';
describe('<Content/>', () => {
describe('render methods', () => {
it('should render correctly', () => {
const mockPost = { ⇽--- 创建组件可以使用的虚post对象
content: 'I am learning to test React components',
};
const wrapper = shallow(<Content post={mockPost} />); ⇽--- 执行组件的浅渲染并保存返回的包装器留待之后使用
});
});
});
现在建立了一个可以对其进行断言的测试组件。进行断言将使用Jest内置的 expect()
函数。如果使用的是其他断言库,可能会用到其他东西。记得之前提到过,这些断言库是为了让断言更简单。例如,检查一个对象是否深层相等(意味着每一个属性都相等)可能是一项复杂的任务。在编写测试时,我们不应该只是为了编写测试而关注于实现大量新功能,而应该关注于被测代码。断言辅助和其他开源库让这点变得更容易。
为了测试手头的组件,需要做一些我们之前思考过的断言:类样式名称、内部内容和元素类型。我们还将使用React Test Renderer来创建一个快照测试。快照测试是Jest的一个功能,它让使用者用一种独特的方式来测试组件的渲染输出。快照测试与可视化回归测试密切相关,可视化回归测试是一个可以用来比较应用程序可视化输出并检查差异的过程。
如果发现图像有差异,就知道测试失败并需要调整,或者至少需要更新输出快照。Jest没有使用图片,它创建了测试的JSON输出并将其存储在特定的目录中。应该将这些与其他代码一起添加到版本控制中。代码清单9-5展示了如何使用Jest、Enzyme和React Test Renderer来编写这些断言。
代码清单9-5 编写断言(src/components/post/Content.test.js)
import React from 'react';
import { shallow } from 'enzyme'; ⇽--- 导入enzyme和react-test-renderer.
import renderer from 'react-test-renderer';
import Content from '../../../src/components/post/Content'; ⇽--- 导入要测试的组件
describe('<Content/>', () => { ⇽--- 使用Jasmine风格的describe函数来将测试组织在一起
test('should render correctly', () => {
const mockPost = { ⇽--- 创建post的mock
content: 'I am learning to test React components'
};
const wrapper = shallow(<Content post={mockPost} />); ⇽--- 使用Enzyme的浅渲染方法来渲染组件
expect(wrapper.find('p').length).toBe(1);
expect(wrapper.find('p.content').length).toBe(1);
expect(wrapper.find('.content').text()).toBe(mockPost.content);
expect(wrapper.find('p').text()).toBe(mockPost.content);
});
test('snapshot', () => { ⇽--- 使用Jest和react-testrenderer创建快照测试
const mockPost = {
content: 'I am learning to test React components'
};
const component = renderer.create(<Content post={mockPost} />); ⇽--- 使用Jest和react-testrenderer创建快照测试
const tree = component.toJSON();
expect(tree).toMatchSnapshot(); ⇽--- 使用Jest和react-testrenderer创建快照测试
});
});
如果测试运行器正在运行,应该会看到来自Jest的结果输出。自测试运行器出现以来,Jest命令行工具有了极大的改进,应该能够在终端里看到有关测试的重要信息。