编写易于测试的 Vue 3 组件

编写易于测试的 Vue 3 组件

Flying
2022-06-06 / 0 评论 / 126 阅读 / 正在检测是否收录...

最近 Vue Test Utils 2.0 正式版终于发布了!Vue Test Utils(VTU)帮助你为 Vue 组件编写测试。然而,VTU 的功能也有限,比如我们无法使用包裹器方法 setData 来修改 script setup 中的响应式数据。以下是一些建议,帮助你编写易于测试的代码,并编写有意义且易于维护的测试。

以下列表提供了一般指导,对于常见情况可能会有所帮助。

tips-vtu.svg

不要测试实现细节

从用户的角度来看,以输入和输出为思考方式。大致上,这是你在为 Vue 组件编写测试时应考虑的所有内容:

输入示例
交互点击、输入等任何“人类”交互
Props组件接收的参数
数据流来自 API 调用、数据订阅等的数据流
输出示例
DOM 元素渲染到文档中的任何可观察节点
事件通过 $emit 触发的事件
副作用例如 console.log 或 API 调用

其他所有内容都是实现细节

请注意,此列表不包括诸如内部方法、中间状态甚至数据等元素。

经验法则是测试不应在重构时失败,也就是说,当我们在不改变行为的情况下更改其内部实现时,测试不应该失败。如果发生这种情况,则测试可能依赖于实现细节。

例如,假设有一个基本的计数器组件,其中包含一个按钮来递增计数器:

<script lang="ts">
  export default {
    data() {
      return { count: 0 }
    },
    methods: {
      increment() {
        this.count++
      }
    }
  }
</script>

<template>
  <p class="paragraph">Times clicked: {{ count }}</p>
  <button @click="increment">increment</button>
</template>

我们可以编写以下测试:

// Counter.test.ts
import { mount } from '@vue/test-utils'
import Counter from './Counter.vue'

test('counter text updates', async () => {
  const wrapper = mount(Counter)
  const paragraph = wrapper.find('.paragraph')
  expect(paragraph.text()).toBe('Times clicked: 0')
  await wrapper.setData({ count: 2 })
  expect(paragraph.text()).toBe('Times clicked: 2')
})

请注意,这里我们更新了其内部数据,并且我们还依赖于细节(从用户的角度来看),例如 CSS 类名。

请注意,更改数据或 CSS 类名都会导致测试失败。但组件仍将按预期工作。这被称为误报(false positive)

相反,下面的测试试图坚持使用上面列出的输入和输出:

// Counter.test.ts

test('text updates on clicking by VTU', async () => {
  const wrapper = mount(Counter)
  expect(wrapper.text()).toContain('Times clicked: 0')
  const button = wrapper.find('button')
  await button.trigger('click')
  await button.trigger('click')
  expect(wrapper.text()).toContain('Times clicked: 2')
})

诸如 Vue Testing Library(VTL)之类的库是基于这些原则构建的。VTL 遵从测试应该更接近用户与应用程序的实际交互,而不仅仅关注内部实现细节。它鼓励开发人员编写与用户行为相对应的测试代码,从而增加测试的可读性和可维护性。

// Counter.test.ts
import { fireEvent, render } from '@testing-library/vue'
import '@testing-library/jest-dom'

test('text updates on clicking by VTL', async () => {
  const { getByText, getByRole } = render(Counter)
  const text = getByText('Times clicked: 0')
  expect(text).toBeInTheDocument()
  const button = getByRole('button', { name: /increment/i })
  await fireEvent.click(button)
  await fireEvent.click(button)
  expect(text).toHaveTextContent('Times clicked: 2')
})

和 VUT 编写的组件测试二相比,VTL 编写的组件测试更语义化,而且更通用。VTL 为包括 React、Angular 和 Vue 等框架封装 API,更适合测试多框架的 UI 组件库。因此强烈推荐尽量使用 VTL 组件测试,不得不处理组件实例时才使用 VUT 。

你的测试越像你的软件的使用方式,你就会更有信心。

尽量不要使用 setData

VTU 2.x 主要目标是测试 Vue 3 组件,目前该工具库对选项式 API 支持要比组合式 API 好,比如包裹器方法 setData 或挂载选项 data 不会修改组合式 API 的 setup() 数据。

将之前的 Counter.vue 替换成下面的 script setup 语法:

<script lang="ts" setup>
  import { ref } from 'vue'
  const count = ref(0)
  const increment = () => {
    count.value++
  }
</script>

<template>
  <p class="paragraph">Times clicked: {{ count }}</p>
  <button @click="increment">increment</button>
</template>

如你所见,测试一会失败。后面两个测试仍会成功,可见编写测试的组件是遵从最佳实践是很重要的。

FAIL src/vtu/easy/TheCounter.test.ts > counter text updates
TypeError: Cannot add property count, object is not extensible
❯ node_modules/@vue/test-utils/dist/vue-test-utils.esm-bundler.mjs:219:25

注意:目前,我们可以通过包裹器当前组件的 setupState 属性来修改 script setup 中的响应式数据:

test('counter text updates', async () => {
  const wrapper = mount(Counter)
  const paragraph = wrapper.find('.paragraph')
  expect(paragraph.text()).toBe('Times clicked: 0')
  wrapper.getCurrentComponent().setupState.count = 2
  await wrapper.vm.$nextTick()
  expect(paragraph.text()).toBe('Times clicked: 2')
})

这个属性藏得很深😁,以后有可能被官方取消,而且也不是最佳实践,慎用。

如果刚好将响应式数据绑定到 input,是可以试一试 setValue() 方法

构建更小、更简单的组件

一个经验法则是,如果组件功能较少,则更容易进行测试。

将组件分解为较小的组件将使它们更易于组合和理解。以下是一些建议,可以使组件更简单。

提取 API 调用

通常,你将在整个应用程序中执行多个 HTTP 请求。从测试的角度来看,HTTP 请求为组件提供了输入,组件也可以发送 HTTP 请求。

如果对测试 API 调用不熟悉,请查看进行 HTTP 请求指南。

提取复杂的方法

有时,组件可能包含复杂的方法、执行繁重的计算或使用多个依赖项。

这里的建议是将此方法提取出来并导入到组件中。这样,你可以使用 Jest 或任何其他测试运行器单独测试该方法。

这样做的另一个好处是,组件更容易理解,因为复杂逻辑被封装在另一个文件中。

此外,如果复杂的方法难以设置或速度较慢,你可能希望模拟它以使测试更简单和更快。进行 HTTP 请求的示例是一个很好的例子-axios 是一个相当复杂的库!

在编写组件之前编写测试

如果在编写代码之前编写测试,就不可能编写无法测试的代码!

入门指南提供了一个示例,说明在编写代码之前编写测试如何导致可测试的组件。它还可以帮助你检测和测试边缘情况。

链接

1

评论 (0)

取消