Vitest 中模拟 Vue Router 的问题

问题描述 投票:0回答:1

我正在使用组合 API 学习 Vue 3,而且我对 Vitest 也很陌生(不过我知道它使用 Vue Test Utils)。

所以,简而言之,我遇到了一个问题,我模拟了 Vue Router,触发了对 router-link 元素的点击,并期望路由器被调用,但它失败了。

首先,为了以防万一,这些是我在可能与此事相关的依赖项上使用的版本:

"dependencies": {
  "vue": "^3.3.2",
  "vue-router": "^4.2.0"
},
"devDependencies": {
  "@vitejs/plugin-vue": "^4.2.3",
  "@vitejs/plugin-vue-jsx": "^3.0.1",
  "@vue/test-utils": "^2.3.2",
  "jsdom": "^22.0.0",
  "start-server-and-test": "^2.0.0",
  "vite": "^4.3.5",
  "vitest": "^0.31.0"
}

我有一个简单的视图来测试这个:

TestView.vue

<template>
  <router-link data-test="test-link" to="login">test link</router-link>
</template>

我已经尝试了几种方法来进行测试,但这里是我认为更有意义的 2 种:

1 - 使用mockImplementationOnce,来自VTU 文档

testView.test.js

import { describe, expect, test, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import { useRouter } from 'vue-router'
import TestView from '../../views/TestView.vue'

vi.mock('vue-router', () => ({
  useRoute: vi.fn(),
  useRouter: vi.fn(() => ({
    push: () => {}
  }))
}))

describe('Test View', () => {
  test('Login button redirects to login view', async () => {
    const push = vi.fn()
    useRouter.mockImplementationOnce(() => ({
      push
    }))

    const wrapper = mount(TestView, {
      global: {
        stubs: ['router-link'] // tried removing this stub aswell
      }
    })

    const btn = wrapper.get('[data-test="test-link"]')
    await btn.trigger('click')
    // Few things to note here:
    // I also tried adding await nextTick() and even a promise with 1s timeout to make sure
    // the button is clicked before the assertion happens, but doesn't help.
    
    // Also it's not like the button isn't found, the following assert succeeds
    expect(btn.attributes('to')).toEqual('login')

    // Output: AssertionError: expected "spy" to be called at least once
    expect(push).toHaveBeenCalled()
  })
})


2-使用mockReturnValue,在这篇文章

中看到

testView.test.js

import { beforeEach, describe, expect, test, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import { useRouter } from 'vue-router'
import TestView from '../../views/TestView.vue'

vi.mock('vue-router')

describe('Test View', () => {
  useRouter.mockReturnValue({
    push: vi.fn()
  })

  beforeEach(() => {
    useRouter().push.mockReset()
  })
  test('Login button redirects to login view', async () => {
    const wrapper = mount(TestView, {
      global: {
        stubs: ['router-link'] // tried removing this stub aswell
      }
    })

    const btn = wrapper.get('[data-test="test-link"]')
    await btn.trigger('click')
    // Few things to note here:
    // I also tried adding await nextTick() and even a promise with 1s timeout to make sure
    // the button is clicked before the assertion happens, but doesn't help.
    
    // Also it's not like the button isn't found, the following assert succeeds
    expect(btn.attributes('to')).toEqual('login')

    // Output: AssertionError: expected "spy" to be called at least once
    expect(useRouter().push).toHaveBeenCalled()
  })
})

因此,在这两种情况下我都会遇到相同的错误:

AssertionError: expected "spy" to be called at least once

我发现的所有其他潜在解决方案都不适合我,其中许多很可能已经过时了。
有人看到我缺少什么或者是否有其他方法可以做到这一点?

提前感谢您,如果我没有完全遵循指南,请抱歉,这是我第一次在这里问问题。

mocking vue-router vue-composition-api vue-test-utils vitest
1个回答
0
投票

我面临着同样的问题,终于找到了一种方法来解决它。让我知道它是否也适合您:

import { afterEach, describe, expect, it, vi } from 'vitest'
import { render } from '@/test-utils'
import userEvent from '@testing-library/user-event'

afterEach(() => {
  vi.clearAllMocks()
})

const mockRoutePush = vi.fn()
vi.mock('vue-router', async () => {
  return {
    RouterView: {},
    useRouter: () => {
      return {
        push: mockRoutePush
      }
    }
  }
})

describe('when ...', () => {
  it('navigates to ...', async () => {
    // set up everything
    const { screen } = render(MyComponent)

    // click on button
    const user = userEvent.setup()
    await user.click(screen.getByRole('button', { name: 'navigate' }))

    // expect that navigation happened
    expect(mockRoutePush).toHaveBeenCalled()
    expect(mockRoutePush).toHaveBeenCalledWith(
      expect.objectContaining({ name: 'route-name' })
    )
  })
})
© www.soinside.com 2019 - 2024. All rights reserved.