Skip to content

触发 HTTP 请求

¥Making HTTP requests

现代测试运行程序在测试 HTTP 请求时已经提供了许多出色的功能。因此,Vue Test Utils 没有任何独特的工具来执行此操作。

¥Modern test runners already provide lots of great features when it comes to test HTTP requests. Thus, Vue Test Utils doesn't feature any unique tool to do so.

然而,这是一个需要测试的重要功能,我们想要强调一些问题。

¥However, it is an important feature to test, and there are a few gotchas we want to highlight.

在本节中,我们将探讨一些执行、模拟和断言 HTTP 请求的模式。

¥In this section, we explore some patterns to perform, mock, and assert HTTP requests.

博客文章列表

¥A list of blog posts

让我们从一个基本用例开始。以下 PostList 组件渲染从外部 API 获取的博客文章列表。为了获取这些帖子,该组件具有触发请求的 button 元素:

¥Let's start with a basic use case. The following PostList component renders a list of blog posts fetched from an external API. To get these posts, the component features a button element that triggers the request:

vue
<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>

为了正确测试这个组件,我们需要做几件事。

¥There are several things we need to do to test this component properly.

我们的第一个目标是在不实际访问 API 的情况下测试该组件。这将造成一个脆弱且可能缓慢的测试。

¥Our first goal is to test this component without actually reaching the API. This would create a fragile and potentially slow test.

其次,我们需要断言组件使用适当的参数进行了正确的调用。我们不会从该 API 获得结果,但我们仍然需要确保我们请求了正确的资源。

¥Secondly, we need to assert that the component made the right call with the appropriate parameters. We won't be getting results from that API, but we still need to ensure we requested the right resources.

另外,我们需要确保 DOM 已相应更新并显示数据。我们通过使用 @vue/test-utils 中的 flushPromises() 函数来实现这一点。

¥Also, we need to make sure that the DOM has updated accordingly and displays the data. We do so by using the flushPromises() function from @vue/test-utils.

js
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 的,你可以阅读有关此行为的更多信息 在他们的文档中

¥Pay attention that we added prefix mock to the variable mockPostList. If not, we will get the error: "The module factory of jest.mock() is not allowed to reference any out-of-scope variables.". This is jest-specific, and you can read more about this behavior in their docs.

另请注意我们如何等待 flushPromises,然后与组件交互。我们这样做是为了确保 DOM 在断言运行之前已更新。

¥Also notice how we awaited flushPromises and then interacted with the Component. We do so to ensure that the DOM has been updated before the assertions run.

jest.mock() 的替代品

在 Jest 中设置模拟有多种方法。上例中使用的方法是最简单的。对于更强大的替代方案,你可能需要查看 axios-mock-adaptermsw 等。

¥There are several ways of setting mocks in Jest. The one used in the example above is the simplest. For more powerful alternatives, you might want to check out axios-mock-adapter or msw, among others.

断言加载状态

¥Asserting loading state

现在,这个 PostList 组件非常有用,但它缺少一些其他很棒的功能。让我们扩展它,使其在加载我们的帖子时显示一条精美的消息!

¥Now, this PostList component is pretty useful, but it lacks some other awesome features. Let's expand it to make it display a fancy message while loading our posts!

另外,我们在加载时也禁用 <button> 元素。我们不希望用户在获取时继续发送请求!

¥Also, let's disable the <button> element while loading, too. We don't want users to keep sending requests while fetching!

vue
<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>

让我们编写一个测试来断言所有与加载相关的元素都按时渲染。

¥Let's write a test to assert that all the loading-related elements are rendered on time.

js
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 requests from Vuex

更复杂应用的典型场景是触发执行 HTTP 请求的 Vuex 操作。

¥A typical scenario for more complex applications is to trigger a Vuex action that performs the HTTP request.

这与上面概述的示例没有什么不同。我们可能希望按原样加载存储并模拟服务,例如 axios。通过这种方式,我们可以模拟系统的边界,从而对我们的测试获得更高的信心。

¥This is no different from the example outlined above. We might want to load the store as is and mock services such as axios. This way, we're mocking our system's boundaries, thus achieving a higher degree of confidence in our tests.

你可以查看 测试 Vuex 文档,了解有关使用 Vue Test Utils 测试 Vuex 的更多信息。

¥You can check out the Testing Vuex docs for more information on testing Vuex with Vue Test Utils.

结论

¥Conclusion

  • Vue Test Utils 不需要特殊的工具来测试 HTTP 请求。唯一需要考虑的是我们正在测试异步行为。

    ¥Vue Test Utils does not require special tools to test HTTP requests. The only thing to take into account is that we're testing asynchronous behavior.

  • 测试不得依赖于外部服务。使用 jest.mock 等模拟工具来避免这种情况。

    ¥Tests must not depend on external services. Use mocking tools such as jest.mock to avoid it.

  • flushPromises() 是一个有用的工具,可确保 DOM 在异步操作后更新。

    ¥flushPromises() is a useful tool to make sure the DOM updates after an async operation.

  • 通过与组件交互直接触发 HTTP 请求使你的测试更具弹性。

    ¥Directly triggering HTTP requests by interacting with the component makes your test more resilient.

Vue Test Utils 中文网 - 粤ICP备13048890号