Mock Vue RouterView/RouterLink in Vitest (kombinierte API)
P粉478188786
P粉478188786 2024-03-27 09:49:56
0
1
397

Ist das aufgrund des Titels möglich?

Ich versuche eine einfache Komponente einer Vue-Anwendung zu testen, die einen Header und eine Schaltfläche enthält, die den Benutzer zur nächsten Seite weiterleitet. Ich möchte testen, ob beim Klicken auf die Schaltfläche ein Ereignis/eine Nachricht an den Router gesendet wird, und prüfen, ob die Zielroute korrekt ist.

Die Bewerbung ist wie folgt aufgebaut:

App.Vue - Einstiegskomponente

<script setup lang="ts">
import { RouterView } from "vue-router";
import Wrapper from "@/components/Wrapper.vue";
import Container from "@/components/Container.vue";
import HomeView from "@/views/HomeView.vue";
</script>

<template>
  <Wrapper>
    <Container>
      <RouterView/>
    </Container>
  </Wrapper>
</template>

<style>

  body{
    @apply bg-slate-50 text-slate-800 dark:bg-slate-800 dark:text-slate-50;
  }

</style>

router/index.ts – Vue Router-Konfigurationsdatei

import {createRouter, createWebHistory} from "vue-router";
import HomeView from "@/views/HomeView.vue";

export enum RouteNames {
    HOME = "home",
    GAME = "game",
}

const routes = [
    {
      path: "/",
      name: RouteNames.HOME,
      component: HomeView,
      alias: "/home"
    },
    {
      path: "/game",
      name: RouteNames.GAME,
      component: () => import("@/views/GameView.vue"),
    },
];

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes
});

export default router;

HomeView.Vue – Die Eingangsansicht von RouterView in der App-Komponente

<template>
  <Header title="My App"/>
  <ButtonRouterLink :to="RouteNames.GAME" text="Start" class="btn-game"/>
</template>

<script setup>
  import Header from "@/components/Header.vue";
  import ButtonRouterLink from "@/components/ButtonRouterLink.vue";
  import {RouteNames} from "@/router/";
</script>

ButtonRouterLink.vue

<template>
<RouterLink v-bind:to="{name: to}" class="btn">
  {{ text }}
</RouterLink>
</template>

<script setup>
import { RouterLink } from "vue-router";

const props = defineProps({
  to: String,
  text: String
})
</script>

Gibt es angesichts der Verwendung von CompositionAPI und TypeScript eine Möglichkeit, den Router in Vitest-Tests zu simulieren?

Hier ist ein Beispiel für eine Testdateistruktur (HomeView.spec.ts):

import {shallowMount} from "@vue/test-utils";
import { describe, test, vi, expect } from "vitest";
import { RouteNames } from "@/router";
import HomeView from "../../views/HomeView.vue";

describe("Home", () => {
    test ('navigates to game route', async () => {
        // Check if the button navigates to the game route

        const wrapper = shallowMount(HomeView);

        await wrapper.find('.btn-game').trigger('click');

        expect(MOCK_ROUTER.push).toHaveBeenCalledWith({ name: RouteNames.GAME });
    });
});

Ich habe mehrere Methoden ausprobiert: vi.mock('vue-router'), vi.spyOn(useRoute,'push'), vue-router-mock-Bibliothek, aber ich kann den Test nicht ausführen, es scheint der Router zu sein ist einfach nicht verfügbar.

Aktualisiert (15.04.23)

Als ich dem Vorschlag von @tao folgte, wurde mir klar, dass das Testen von Klickereignissen in HomeView kein guter Test war (es war die Bibliothek, die getestet wurde, nicht meine App), also habe ich einen Test ButtonRouterLink hinzugefügt, um zu sehen, ob Vue RouterLink richtig gerendert wird , mit to Parametern:

import {mount, shallowMount} from "@vue/test-utils";
import { describe, test, vi, expect } from "vitest";
import { RouteNames } from "@/router";
import ButtonRouterLink from "../ButtonRouterLink.vue";

vi.mock('vue-router');

describe("ButtonRouterLink", () => {
    test (`correctly transforms 'to' param into a router-link prop`, async () => {
       
        const wrapper = mount(ButtonRouterLink, {
            props: {
                to: RouteNames.GAME
            }
        });

        expect(wrapper.html()).toMatchSnapshot();
    });
});

Es rendert eine leere HTML-Zeichenfolge""

exports[`ButtonRouterLink > correctly transforms 'to' param into a router-link prop 1`] = `""`;

Begleitet von einer nicht so hilfreichen Vue-Warnung (erwarteter Typ nicht angegeben):

[Vue warn]: Invalid prop: type check failed for prop "to". Expected , got Object  
  at <RouterLink to= { name: 'game' } class="btn btn-blue" > 
  at <ButtonRouterLink to="game" ref="VTU_COMPONENT" > 
  at <VTUROOT>
[Vue warn]: Invalid prop: type check failed for prop "ariaCurrentValue". Expected , got String with value "page". 
  at <RouterLink to= { name: 'game' } class="btn btn-blue" > 
  at <ButtonRouterLink to="game" ref="VTU_COMPONENT" > 
  at <VTUROOT>
[Vue warn]: Component is missing template or render function. 
  at <RouterLink to= { name: 'game' } class="btn btn-blue" > 
  at <ButtonRouterLink to="game" ref="VTU_COMPONENT" > 
  at <VTUROOT>

P粉478188786
P粉478188786

Antworte allen(1)
P粉759451255

这是可能的

你的示例和官方示例之间有一个重要的区别:在被测试的组件中,你将其放置在了一个中间组件中,而不是使用RouterLink(或使用router.push的按钮)。

这会如何改变单元测试?

第一个问题是你使用了shallowMount,它会用空容器替换子组件。在你的情况下,这意味着ButtonRouterLink不再包含RouterLink,并且不会对点击事件做任何操作,它只是接收点击事件。

解决这个问题的选项有:

  • a) 使用mount而不是shallowMount,将其变成集成测试而不是单元测试
  • b) 将这个功能的测试移动到ButtonRouterLink的测试中。一旦测试了ButtonRouterLink的任何:to是否正确转换为{ name: to }并传递给RouterLink,你只需要测试这些:to是否正确传递给使用它们的任何组件中的<ButtonRouterLink />

将这个功能移动到自己的组件中所创建的第二个问题稍微微妙一些。通常,我期望以下ButtonRouterLink的测试可以工作:

import { RouterLinkStub } from '@vue/test-utils'

const wrapper = shallowMount(YourComp, {
  stubs: {
    RouterLink: RouterLinkStub
  }
})

expect(wrapper.findComponent(RouterLinkStub).props('to')).toEqual({
  name: RouterNames.GAME
})

令我惊讶的是,这个测试失败了,它声称在RouterLinkStub中接收到的:to属性值不是{ name: RouterNames.GAME },而是'game',这是我传递给ButtonRouterLink的值。就像我们的组件从未将value转换为{ name: value }一样。

相当奇怪。
事实证明,问题是<RouterLink />恰好是我们组件的根元素。这意味着组件(<BottomRouterLink>)在DOM中被<RouterLinkStub>替换,但:to的值是从前者而不是后者读取的。简而言之,如果模板是这样的,测试就会成功:

  <span>
    <RouterLink ... />
  </span>

为了解决这个问题,我们需要导入实际的RouterLink组件(来自vue-router),并找到,而不是找到RouterLinkStub。为什么?@vue/test-utils足够聪明,可以找到存根组件而不是实际组件,但是这样,.findComponent()只会匹配替换后的元素,而不是在它仍然是ButtonRouterLink(未处理)时。此时,:to的值已经被解析为RouterLink实际接收的值。

完整的测试如下:

import { shallowMount } from '@vue/test-utils'
import { describe, it, expect } from 'vitest'
import ButtonRouterLink from '../src/ButtonRouterLink.vue'
import { RouterLinkStub } from '@vue/test-utils'
import { RouterLink } from 'vue-router'

const TEST_STRING = 'this is a test string'

describe('ButtonRouterLink', () => {
  it(`should transform 'to' into '{ name: to }'`, () => {
    const wrapper = shallowMount(ButtonRouterLink, {
      props: {
        to: TEST_STRING
      },
      stubs: {
        RouterLink: RouterLinkStub
      }
    })

    expect(wrapper.findComponent(RouterLink).props('to')).toEqual({
      name: TEST_STRING
    })
  })
})

有点棘手。

Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage
Über uns Haftungsausschluss Sitemap
Chinesische PHP-Website:Online-PHP-Schulung für das Gemeinwohl,Helfen Sie PHP-Lernenden, sich schnell weiterzuentwickeln!