Das Javascript-Framework Vue.js, Teil 13 Unit-Testing von Vue-Komponenten

Von Dr. Dirk Koller

Für Vue.js existieren großartige Lösungen, die den Entwickler beim Testing unterstützen. Dieser Beitrag beschäftigt sich mit Unit-Tests auf Basis der Vue Test Utils, das Ende-zu-Ende-Testing thematisieren wir dann in einem der kommenden Artikel.

Die Test-Utils für Vue.js vereinfachen das Unit-Testing deutlich.
Die Test-Utils für Vue.js vereinfachen das Unit-Testing deutlich.
(Bild: Mudassar Iqbal (kreatikar) / Pixabay)

Praktisch jeder Projektbeteiligte hält UI-Tests für wichtig, dennoch findet man in der Praxis erstaunlich viele Projekte, die darauf verzichten (oder in irgendeiner dunklen Ecke des Quellcodes veraltete, auskommentierte Tests verstecken). Kein Wunder, Tests für graphische Benutzeroberflächen sind oft schwieriger zu erstellen als etwa Tests für ein Rechenmodul.

Unit-Tests testen kleine Codeteile, das können zum Beispiel Klassen, Methoden oder Funktionen sein. In Vue.js sind diese kleinen Einheiten die Vue-Komponenten, die am besten als Black Box, also nur über ihr öffentliches Interface getestet werden.

Für das Vue.js-Framework ist Vue Test Utils die offizielle Bibliothek zur Entwicklung von Unit-Tests. Die Werkzeuge ermöglichen das Hochfahren und Agieren mit Vue-Komponenten, ohne dass dafür die ganze Anwendung gestartet werden muss. Ausgeführt werden die Tests bevorzugt mit Jest, das ist ein von Facebook entwickelter Test Runner.

Einrichten von Test Utils und Jest

In einem neuen Projekt lässt sich die Testunterstützung einfach mithilfe des Vue CLI-Assistenten einrichten. Dazu wählt man nach Eingabe des „vue create“-Kommandos statt des Default-Templates die Option „Manually select features“, anschließend „Unit Testing“ und im weiteren Verlauf unter „Pick a unit testing solution“-Jest.

In den meisten Fällen wird man es aber eher mit einem bereits vorhandenen Projekt zu tun haben. Hier dient als Beispiel ein einfaches Vue3-Projekt, das mit dem Vue CLI (Template Default) angelegt wird:

vue create testing

Für die Testunterstützung in einem bestehenden Projekt installiert man am besten zunächst das Plug-in cli-plugin-unit-jest in den DevDependencies (im Projektordner ausführen):

npm install @vue/cli-plugin-unit-jest -D

Anschließend kann dann mittels des „vue add“-Kommandos die Test-Unterstützung für das Projekt aktiviert werden:

vue add unit-jest

Aufbau von Tests

Danach hat sich im Projektordner einiges verändert. Unter „src/tests/unit“ wurde eine Datei namens example.spec.js mit folgendem Inhalt angelegt:

import { shallowMount } from '@vue/test-utils'
import HelloWorld from '@/components/HelloWorld.vue'
describe('HelloWorld.vue', () => {
  it('renders props.msg when passed', () => {
    const msg = 'new message'
    const wrapper = shallowMount(HelloWorld, {
      propsData: { msg }
    })
    expect(wrapper.text()).toMatch(msg)
  })
})

Diese Datei enthält einen Beispieltest. Der Aufbau ist nicht weiter kompliziert. Nach den Importen wird mit der Funktion describe die Testsuite beschrieben, hier für die Komponente „HelloWorld.vue“. In diesem Fall ist es nur ein einziger Test, der in der Funktion it formuliert wird. Der initial übergebene String („renders props.msg when passed“) dient zur Beschreibung und Identifizierung des Tests.

Im eigentlichen Code-Teil wird nach der Definition der Konstante msg mithilfe der Funktion shallowMount() die standardmäßig mit dem CLI angelegte HelloWorld-Komponente gemountet, gerendert und ihr dabei im Objekt nach dem Namen als propsData die Konstante mit der Nachricht übergeben. Im Objekt können weitere Eigenschaften wie mocks, stubs und methods gesetzt werden, Details dazu finden sich unter MountingOptions in der Dokumentation.

Die Funktion shallowMount(), übersetzt etwa „seichtes Mounten“, fährt dabei eventuelle Child-Komponenten nicht mit hoch, diese liegen nur als Stubs vor. Das geht schnell, funktioniert aber nicht für Tests, die mit Child-Komponenten interagieren sollen. Um die Child-Komponenten mit hochzufahren, nutzt man stattdessen mount(). Beide liefern den Komponenten-Wrapper zurück, über den man Zugriff auf zahlreiche Properties und Methoden erhält.

Über html() beispielsweise erhält man Zugriff auf den DOM-Knoten, mittels vm kann man auf die Vue-Instanz zugreifen. Im Beispiel wird über den Wrapper auf text zugegriffen und in der Funktion expect() geprüft, ob der Textinhalt der Komponente mit der übergebenen Message übereinstimmt. Die Dokumentation zum Wrapper listet zahlreiche andere Properties und Methoden auf.

Run Test, run!

In der Datei package.json hat sich ebenfalls etwas getan, hier wurde unter Scripts folgender Eintrag zugefügt:

scripts: {
  ...
  "test:unit": "vue-cli-service test:unit",
  ...
}

Das Ausführen der Tests erfolgt somit durch folgende Eingabe:

npm run test:unit

Das kann nun schon ausprobiert werden. Der Ausgabe ist zu entnehmen, dass der einzig vorhandene Test erfolgreich durchgeführt wurde:

> vue-cli-service test:unit PASS tests/unit/example.spec.js
  HelloWorld.vue
    √ renders props.msg when passed (29 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 2.072 s
Ran all test suites.

Eigene Testsuiten müssen ebenfalls die Endung spec.js erhalten, damit sie vom Test Runner ohne weitere Anpassungen gefunden werden. Der Name der zu testenden Komponente wird üblicherweise vorangestellt, also beispielsweise hello-world.spec.js. Mehrere Tests für eine Komponente werden in der gleichen Datei durch mehrere kommaseparierte it-Funktionen implementiert. Bei mehreren Tests für eine Komponente empfiehlt sich das Mounten in der Methode beforeEach(), das Callback-Argument wird vor jedem Test ausgeführt:

Jetzt Newsletter abonnieren

Täglich die wichtigsten Infos zu Softwareentwicklung und DevOps

Mit Klick auf „Newsletter abonnieren“ erkläre ich mich mit der Verarbeitung und Nutzung meiner Daten gemäß Einwilligungserklärung (bitte aufklappen für Details) einverstanden und akzeptiere die Nutzungsbedingungen. Weitere Informationen finde ich in unserer Datenschutzerklärung.

Aufklappen für Details zu Ihrer Einwilligung
import { shallowMount } from '@vue/test-utils';
import HelloWorld from '@/components/HelloWorld.vue'
let wrapper;beforeEach(() => {
  wrapper = shallowMount(HelloWorld, {
    propsData: { msg },
    mocks: {},
    stubs: {},
    methods: {},
  });
});
describe('HelloWorld.vue', () => {
  ...
})

Analog lässt sich mit afterEach() nach jedem Test aufräumen.

Testen von asynchronem Verhalten

Für ein zweites Beispiel wird die HelloWorld-Komponente um einen Button erweitert. Wird der Button geklickt, soll der String in message abgeändert werden. Dies geschieht in der Methode changeText():

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <button @click="changeText()">Klick me!</button>
...
<script>
export default {
  name: 'HelloWorld',
  props: {
    msg: String
  },
  data() {
    return {
      message: 'hallo'
    }
  },
  methods: {
    changeText() {
      this.message = "changed!";
    }
  }
}
</script>

Ein Test, der dieses Verhalten überprüft, könnte folgendermaßen aussehen:

it('triggers a click', async () => {
  const msg = 'new message'
  const wrapper = shallowMount(HelloWorld, {
    propsData: { msg }
  })
  await wrapper.find('button').trigger('click')
  expect(wrapper.vm.message).toMatch('changed!')
})

Der Test sucht im Wrapper mit einem Selector nach dem vorhandenen Button und löst einen Klick aus, woraufhin message in changeText() auf den Wert „changed!“ geändert wird. Da hier eine reaktive Property (message) geändert wird, muss der Test angewiesen werden, die damit verbundenen Updates von Vue abzuwarten. Das geschieht durch das vorangestellte await. In expect() wird schließlich auf die Variable message der Vue-Instanz zugegriffen und überprüft, ob der Wert auch wirklich geändert wurde.

Der Beitrag beschreibt lediglich die grundlegende Funktionalität von Jest und den Vue Test Utils. Weitere Beispiele und hilfreiche Informationen finden sich in der Dokumentation des Test-Utils-Projekts.

(ID:48486460)