发出 HTTP 请求
现代测试运行器在测试 HTTP 请求方面已经提供了许多很棒的功能。因此,Vue 测试工具没有提供任何独特的工具来做到这一点。
但是,这是一个重要的测试功能,我们想强调一些需要注意的地方。
在本节中,我们将探讨一些执行、模拟和断言 HTTP 请求的模式。
博客文章列表
让我们从一个基本用例开始。以下 PostList
组件渲染从外部 API 获取的博客文章列表。为了获取这些文章,该组件包含一个 button
元素,它会触发请求
<template>
<button @click="getPosts">Get posts</button>
<ul>
<li v-for="post in posts" :key="post.id" data-test="post">
{{ post.title }}
</li>
</ul>
</template>
<script>
import axios from 'axios'
export default {
data() {
return {
posts: null
}
},
methods: {
async getPosts() {
this.posts = await axios.get('/api/posts')
}
}
}
</script>
我们需要做几件事才能正确测试此组件。
我们的第一个目标是测试此组件 **无需实际访问 API**。这将创建一个脆弱且可能很慢的测试。
其次,我们需要断言该组件使用适当的参数进行了正确的调用。我们不会从该 API 获取结果,但我们仍然需要确保我们请求了正确的资源。
此外,我们需要确保 DOM 已相应更新并显示数据。我们通过使用 @vue/test-utils
中的 flushPromises()
函数来做到这一点。
import { mount, flushPromises } from '@vue/test-utils'
import axios from 'axios'
import PostList from './PostList.vue'
const mockPostList = [
{ id: 1, title: 'title1' },
{ id: 2, title: 'title2' }
]
// Following lines tell Jest to mock any call to `axios.get`
// and to return `mockPostList` instead
jest.spyOn(axios, 'get').mockResolvedValue(mockPostList)
test('loads posts on button click', async () => {
const wrapper = mount(PostList)
await wrapper.get('button').trigger('click')
// Let's assert that we've called axios.get the right amount of times and
// with the right parameters.
expect(axios.get).toHaveBeenCalledTimes(1)
expect(axios.get).toHaveBeenCalledWith('/api/posts')
// Wait until the DOM updates.
await flushPromises()
// Finally, we make sure we've rendered the content from the API.
const posts = wrapper.findAll('[data-test="post"]')
expect(posts).toHaveLength(2)
expect(posts[0].text()).toContain('title1')
expect(posts[1].text()).toContain('title2')
})
请注意,我们在变量 mockPostList
前添加了前缀 mock
。如果没有,我们将收到错误:“jest.mock() 的模块工厂不允许引用任何超出范围的变量”。这是 jest 特定的,你可以阅读更多关于这种行为的信息 在他们的文档中。
还要注意我们如何等待 flushPromises
然后与组件交互。我们这样做是为了确保在断言运行之前 DOM 已更新。
jest.mock() 的替代方案
在 Jest 中有几种设置模拟的方法。上面示例中使用的方法是最简单的。对于更强大的替代方案,你可能想查看 axios-mock-adapter 或 msw,等等。
断言加载状态
现在,这个 PostList
组件非常有用,但它缺少一些其他很棒的功能。让我们扩展它,使其在加载文章时显示一条花哨的消息!
此外,让我们在加载时禁用 <button>
元素。我们不希望用户在获取数据时不断发送请求!
<template>
<button :disabled="loading" @click="getPosts">Get posts</button>
<p v-if="loading" role="alert">Loading your posts…</p>
<ul v-else>
<li v-for="post in posts" :key="post.id" data-test="post">
{{ post.title }}
</li>
</ul>
</template>
<script>
import axios from 'axios'
export default {
data() {
return {
posts: null,
loading: null
}
},
methods: {
async getPosts() {
this.loading = true
this.posts = await axios.get('/api/posts')
this.loading = null
}
}
}
</script>
让我们编写一个测试来断言所有与加载相关的元素都按时渲染。
test('displays loading state on button click', async () => {
const wrapper = mount(PostList)
// Notice that we run the following assertions before clicking on the button
// Here, the component should be in a "not loading" state.
expect(wrapper.find('[role="alert"]').exists()).toBe(false)
expect(wrapper.get('button').attributes()).not.toHaveProperty('disabled')
// Now let's trigger it as usual.
await wrapper.get('button').trigger('click')
// We assert for "Loading state" before flushing all promises.
expect(wrapper.find('[role="alert"]').exists()).toBe(true)
expect(wrapper.get('button').attributes()).toHaveProperty('disabled')
// As we did before, wait until the DOM updates.
await flushPromises()
// After that, we're back at a "not loading" state.
expect(wrapper.find('[role="alert"]').exists()).toBe(false)
expect(wrapper.get('button').attributes()).not.toHaveProperty('disabled')
})
来自 Vuex 的 HTTP 请求
对于更复杂的应用程序,一个典型的场景是触发执行 HTTP 请求的 Vuex 操作。
这与上面概述的示例没有什么不同。我们可能希望按原样加载商店并模拟 axios
等服务。这样,我们就可以模拟系统的边界,从而在测试中获得更高的信心。
你可以查看 测试 Vuex 文档,以获取有关使用 Vue 测试工具测试 Vuex 的更多信息。
结论
- Vue 测试工具不需要特殊的工具来测试 HTTP 请求。唯一需要考虑的是我们正在测试异步行为。
- 测试不能依赖于外部服务。使用
jest.mock
等模拟工具来避免这种情况。 flushPromises()
是一个有用的工具,可以确保 DOM 在异步操作后更新。- 通过与组件交互直接触发 HTTP 请求可以使你的测试更具弹性。