使用 TDD 创建 React 组件
和我一起阅读这篇文章,我将使用测试驱动开发 (TDD) 方法创建一个 React 组件。
我将创建一个确认组件,它具有以下功能:
- 静态标题
- 确认问题 - 这可以是应用程序想要确认的任何问题
- 确认按钮,支持外部处理程序
- 取消按钮,支持外部处理程序
这两个按钮都不知道点击后会发生什么,因为这超出了组件的职责范围。但组件应该启用其他使用它的组件/容器,以便为这些按钮提供回调。
代码如下:
那么,让我们开始吧。TDD
的流程是一个循环:编写测试 => 观察它失败 => 编写使其通过的最少代码 => 观察它成功 => 重构(如果需要) => 重复,这就是我在这里要练习的内容。在某些时候,你可能会觉得这很乏味,甚至不切实际,但我坚持按部就班地去做,让你自己决定它是否能很好地满足你的目的,或者你是否想在过程中偷工减料。
我们先从测试文件开始。我把 Jest 测试环境设置为监视模式,并在其中创建了名为“Confirmation”的组件目录和一个“index.test.js”文件。
第一个测试比较抽象。我想检查渲染组件时是否会渲染某些内容(任何内容),以确保我的组件存在。实际上,我会渲染我的(目前尚不存在的)组件,看看能否通过其“dialog”角色在文档中找到它:
import React from 'react';
import {render} from '@testing-library/react';
describe('Confirmation component', () => {
it('should render', () => {
const {getByRole} = render(<Confirmation />);
expect(getByRole('dialog')).toBeInTheDocument();
});
});
嗯,你猜对了——Jest 不知道“确认”是什么,没错。让我们创建一个刚好满足这个测试要求的组件:
import React from 'react';
const Confirmation = () => {
return <div role="dialog"></div>;
};
export default Confirmation;
我把这个组件导入到测试中,现在测试通过了。太棒了。
接下来,我们需要为这个组件添加一个标题。在本演示中,标题是静态的,应该显示“确认”。让我们为它创建一个测试:
it('should have a title saying "Confirmation"', () => {
const {getByText} = render(<Confirmation />);
expect(getByText('Confirmation')).toBeInTheDocument();
});
测试失败,现在我们编写代码使其通过:
import React from 'react';
const Confirmation = () => {
return (
<div role="dialog">
<h1>Confirmation</h1>
</div>
);
};
export default Confirmation;
接下来是下一个功能,我们希望确保此组件中有一个确认问题。我希望这个问题是动态的,以便可以从组件外部提出。我认为将这个问题作为“确认”组件的“子组件”是正确的做法,因此测试如下:
it('should have a dynamic confirmation question', () => {
const question = 'Do you confirm?';
const {getByText} = render(<Confirmation>{question}</Confirmation>);
expect(getByText(question)).toBeInTheDocument();
});
测试再次失败,因此我编写代码使其通过:
import React from 'react';
const Confirmation = ({children}) => {
return (
<div role="dialog">
<h1>Confirmation</h1>
<div>{children}</div>
</div>
);
};
export default Confirmation;
接下来是按钮。我先从确认按钮开始。我们首先要检查组件上是否有一个显示“OK”的按钮。从现在开始,我会先写测试,然后再写满足测试要求的代码:
测试:
it('should have an "OK" button', () => {
const {getByRole} = render(<Confirmation />);
expect(getByRole('button', {name: 'OK'})).toBeInTheDocument();
});
我在这里使用“名称”选项,因为我知道这个组件中至少还会有一个按钮,而且我需要更具体地说明我想要断言的是哪一个
成分:
import React from 'react';
const Confirmation = ({children}) => {
return (
<div role="dialog">
<h1>Confirmation</h1>
<div>{children}</div>
<button>OK</button>
</div>
);
};
export default Confirmation;
让我们对“取消”按钮做同样的事情:
测试:
it('should have an "Cancel" button', () => {
const {getByRole} = render(<Confirmation />);
expect(getByRole('button', {name: 'Cancel'})).toBeInTheDocument();
});
成分:
import React from 'react';
const Confirmation = ({children}) => {
return (
<div role="dialog">
<h1>Confirmation</h1>
<div>{children}</div>
<button>OK</button>
<button>Cancel</button>
</div>
);
};
export default Confirmation;
好的,很棒。
这样我们就让组件渲染出我们想要的效果了(没有设置样式,不过那是另外一回事)。现在我想确保能够从外部传递该组件按钮的处理程序,并确保在点击按钮时调用它们。
我将从“OK”按钮的测试开始:
it('should be able to receive a handler for the "OK" button and execute it upon click', () => {
const onConfirmationHandler = jest.fn();
const {getByRole} = render(<Confirmation onConfirmation={onConfirmationHandler} />);
const okButton = getByRole('button', {name: 'OK'});
fireEvent.click(okButton);
expect(onConfirmationHandler).toHaveBeenCalled();
});
我的做法是创建一个 spy 函数,将其作为“onConfirmation”处理程序传递给组件,模拟点击“OK”按钮,并断言 spy 函数已被调用。
测试显然失败了,以下是使其正常运行的代码:
import React from 'react';
const Confirmation = ({children, onConfirmation}) => {
return (
<div role="dialog">
<h1>Confirmation</h1>
<div>{children}</div>
<button onClick={onConfirmation}>
OK
</button>
<button>Cancel</button>
</div>
);
};
export default Confirmation;
好的,让我们对“取消”按钮做同样的事情:
测试:
it('should be able to receive a handler for the "Cancel" button and execute it upon click', () => {
const onCancellationHandler = jest.fn();
const {getByRole} = render(<Confirmation onCancellation={onCancellationHandler} />);
const cancelButton = getByRole('button', {name: 'Cancel'});
fireEvent.click(cancelButton);
expect(onCancellationHandler).toHaveBeenCalled();
});
成分:
import React from 'react';
const Confirmation = ({children, onConfirmation, onCancellation}) => {
return (
<div role="dialog">
<h1>Confirmation</h1>
<div>{children}</div>
<button onClick={onConfirmation}>
OK
</button>
<button onClick={onCancellation}>
Cancel
</button>
</div>
);
};
export default Confirmation;
这是完整的测试文件:
import React from 'react';
import {render, fireEvent} from '@testing-library/react';
import Confirmation from '.';
describe('Confirmation component', () => {
it('should render', () => {
const {getByRole} = render(<Confirmation />);
expect(getByRole('dialog')).toBeInTheDocument();
});
it('should have a title saying "Confirmation"', () => {
const {getByText} = render(<Confirmation />);
expect(getByText('Confirmation')).toBeInTheDocument();
});
it('should have a dynamic confirmation question', () => {
const question = 'Do you confirm?';
const {getByText} = render(<Confirmation>{question}</Confirmation>);
expect(getByText(question)).toBeInTheDocument();
});
it('should have an "OK" button', () => {
const {getByRole} = render(<Confirmation />);
expect(getByRole('button', {name: 'OK'})).toBeInTheDocument();
});
it('should have an "Cancel" button', () => {
const {getByRole} = render(<Confirmation />);
expect(getByRole('button', {name: 'Cancel'})).toBeInTheDocument();
});
it('should be able to receive a handler for the "OK" button and execute it upon click', () => {
const onConfirmationHandler = jest.fn();
const {getByRole} = render(<Confirmation onConfirmation={onConfirmationHandler} />);
const okButton = getByRole('button', {name: 'OK'});
fireEvent.click(okButton);
expect(onConfirmationHandler).toHaveBeenCalled();
});
it('should be able to receive a handler for the "Cancel" button and execute it upon click', () => {
const onCancellationHandler = jest.fn();
const {getByRole} = render(<Confirmation onCancellation={onCancellationHandler} />);
const cancelButton = getByRole('button', {name: 'Cancel'});
fireEvent.click(cancelButton);
expect(onCancellationHandler).toHaveBeenCalled();
});
});
我想就是这样了!我们已经实现了组件的所有构建块和逻辑,并进行了全面测试:
是的,我知道,风格不对,但我们可以修复这个问题,只要我们确定我们的构建块完好无损并且一切都按照规格工作即可。
除了陪我一起用 TDD 创建这个组件之外,这篇文章还清晰地证明了 TDD 在开发 UI 组件时可以应用,而且相当容易。TDD 将逐步指导你完成组件功能规范,帮助你专注于重要的事情,同时为未来的重构提供安全保障。这真是太棒了!
与往常一样,如果您对如何改进这一点或任何其他技术有任何想法,请务必与我们分享!
干杯
嘿!如果你喜欢刚才读到的内容,可以在推特上关注@mattibarzeev 🍻
文章来源:https://dev.to/mbarzeev/creating-a-react-component-with-tdd-2jn8