Vue Test Utils 使用技巧

Vue Test Utils 使用技巧

Flying
2020-10-29 / 0 评论 / 142 阅读 / 正在检测是否收录...

最近 Vue Test Utils 2.0 已出了 Beta 版。1.x 已经很稳定了,它很容易上手,也很适合做单元测试,强烈建议你试一试。本文将讲述一些 Vue Test Utils 的提示、技巧,它们可能是你已经知道的也可能是还不知道的。

fix-bug-vue.svg

使用 await nextTick()

在测试中经常会看到在触发事件后使用 nextTick() 的代码:

async function someTest() {
  wrapper = mount(Component)
  wrapper.trigger('click')
  await wrapper.vm.nextTick()
  expect(something).toBe(true)
}

这可以更简洁一些,因为 trigger() 本身就返回 wrapper.vm.nextTick()(即一个 Promise)。

因此,你可以摆脱 await nextTick(),只需等待触发的调用:

async function someTest() {
  wrapper = mount(Component)
  await wrapper.trigger('click')
  expect(something).toBe(true)
}

你还可以等待以下其他调用的结果:

  • setChecked()
  • setData()
  • setSelected()
  • setProps()
  • setValue()

使用 await flushPromises()

1nextTick1 在确保响应式数据的更改在继续测试之前反映在 DOM 中方面非常有用。然而,有时您可能还希望确保其他非 Vue 相关的异步行为也已完成。

一个常见的例子是返回 Promise 的函数。也许您使用 jest.mock 来模拟 axios HTTP 客户端:

jest.spyOn(axios, 'get').mockResolvedValue({ data: 'some mocked data!' })

在这种情况下,Vue 并不知道未解决的 Promise,因此调用 nextTick 不起作用——你的断言可能在 Promise 解决之前运行。对于这种情况,Vue Test Utils 提供了 flushPromises 方法,它可以立即解决所有未完成的Promise。

让我们看一个示例:

import { flushPromises } from '@vue/test-utils'
import axios from 'axios'

jest.spyOn(axios, 'get').mockResolvedValue({ data: 'some mocked data!' })

test('uses a mocked axios HTTP client and flushPromises', async () => {
  // 使用 `axios` 在 `created` 中 进行 HTTP 调用的组件
  const wrapper = mount(AxiosComponent)

  await flushPromises() // axios promise 立即被解决

  // 在上面的代码行之后,axios 请求已经用模拟数据进行了解析。
})
一个易于遵循的规则是在诸如 triggersetProps 的变更时始终使用 await。如果你的代码依赖一些诸如 axios 的异步操作,也要为 flushPromises 加入一个 await

不要在 nextTick() 中使用回调函数

请注意,在 nextTick() 中使用回调函数将意味着错误无法被捕获。

在谈论 nextTick() 的同时,有一个重要的事实需要注意。如果在 nextTick() 中有一个回调函数,并且抛出错误,它将不会显示在测试结果中。

为了解决这个问题,有两件事情可以做——参考以下代码:

// 这不会被发现错误
it('will time out', (done) => {
  Vue.nextTick(() => {
    expect(true).toBe(false)
    done()
  })
})
// 下面三个测试将按预期工作
it('will catch the error using done', (done) => {
  Vue.config.errorHandler = done
  Vue.nextTick(() => {
    expect(true).toBe(false)
    done()
  })
})
it('will catch the error using a promise', () => {
  return Vue.nextTick().then(function () {
    expect(true).toBe(false)
  })
})
it('will catch the error using async/await', async () => {
  await Vue.nextTick()
  expect(true).toBe(false)
})

去除过渡效果

过渡效果可能会导致测试变得困难。除非你特别测试一些过渡功能,否则通常最好去除过渡效果。

// Stub函数只会渲染子组件
const transitionStub = () => ({
  render: function (h) {
    return this.$options._renderChildren
  }
})
test('should render Foo, then hide it', async () => {
  const wrapper = mount(Foo, {
    stubs: {
      // 去除过渡效果
      transition: transitionStub()
    }
  })
  expect(wrapper.text()).toMatch(/Foo/)
})

$route 和 $router 属性是只读的

在使用 Vue Router 进行测试时,请注意如果使用 createLocalVue 并在其上使用 Router,$route$router 属性是只读的。

如果你开始测试涉及到 Vue Router 的内容,通常会使用 createLocalVue:

import { shallowMount, createLocalVue } from '@vue/test-utils'
import VueRouter from 'vue-router'
const localVue = createLocalVue()
localVue.use(VueRouter)
shallowMount(Component, {
  localVue
})

请记住,当你这样做时,它会使 $route(当前路由)和 $router(路由本身)成为只读属性。如果你已经使用了 localVue.use(VueRouter) 并在挂载时使用它,你将无法设置模拟的路由数据。

以下代码是一个无法工作的示例!

import { shallowMount, createLocalVue } from '@vue/test-utils'
import VueRouter from 'vue-router'
const localVue = createLocalVue()
localVue.use(VueRouter)
const $route = {
  path: '/some/path'
}
const wrapper = shallowMount(Component, {
  localVue,
  mocks: {
    // 设置 $route 无效!当使用localVue时,它被设置为只读。
    $route
  }
})

使用 render() 或 renderToString()

如果在服务器上进行测试并且需要测试渲染后的 HTML,请使用 render() 或 `renderToString()。

render() 在内部使用 vue-server-renderer 将组件渲染为静态 HTML - 这包含在 @vue/server-test-utils 包中。

const wrapper = await render(SomeComponent)
expect(wrapper.text()).toContain('<div></div>')

还有一个 renderToString() 函数。

使用 is() 检查组件的类型

有时候你需要检查 mount() 的输出或者 find() 的结果是否是某种特定的组件类型。为此,你可以使用 is

const wrapper = mount(SomeComponent)
expect(wrapper.is(SomeComponent)).toBe(true)
expect(wrapper.find('.modal').is(ModalComponent)).toBe(true)

使用 ref 或 name 查找组件

findComponent() 函数具有按照 Vue 组件 ref 标识符或名称搜索元素的能力。

const wrapper = mount(SomeComponent)
const btn = wrapper.find({ ref: 'myButton' })
const input = wrapper.find({ name: 'myInput' })

使用 config.silent 选项抑制 Vue 的警告信息

你可能会认为不应该这样做。但是为了避免测试中出现有关变更 props 或其他警告的通知,可以使用 config.silent = true 选项。

import { config } from '@vue/test-utils'
config.silent = true

使用 exists()

如果你需要检查 或 WrapperArray 是否为空,可以使用 exists()

const wrapper = mount(ContactForm)
expect(wrapper.exists()).toBe(true)
const buttons = wrapper.findAll('button')
const specificButton = wrapper.find('button.submit-button')
expect(buttons.exists()).toBe(true) // WrapperArray 非空
expect(specificButton.exists()).toBe(true)

使用 getComponent()

findComponent() 可能返回一个空结果。如果你需要确保它找到了内容(否则测试将失败),可以使用 getComponent()

// 如果 `getComponent` 没有找到任何元素将会抛出一个而错误。`findComponent` 则不会做任何事。
expect(wrapper.getComponent(Bar)) // => gets Bar by component instance
expect(wrapper.getComponent({ name: 'bar' })) // => gets Bar by `name`
expect(wrapper.getComponent({ ref: 'bar' })) // => gets Bar by `ref`

data 合并

使用 data 配置进行挂载时,它将与现有的 data 函数合并。

const TheComponent = {
  template: '<div>{{ foo }}///{{ bar }}</div>',
  data() {
    return {
      foo: 'COMPONENT_FOO',
      bar: 'COMPONENT_BAR'
    }
  }
}
const wrapper = mount(TheComponent, {
  // 将被合并/覆盖的额外数据
  data(){
    return {
      bar: 'FROM_MOUNT_DATA'
    }
  }
});
expect(wrapper.text()).toBe('COMPONENT_FOO///FROM_MOUNT_DATA')
0

评论 (0)

取消