Vue Test Utils Kılavuzu

Atakan Tekoğlu
Modanisa Engineering
7 min readFeb 21, 2022

Bölüm 2

Önceki yazımda “mounting a component, props, factory function, computed properties, emitted events” gibi konulara değinmiştim.

Bu yazımda hangi konuları ele alacağım?

  • Mocking Global Objects
  • Triggering Events
  • Mocking a HTTP Client

Mocking Global Objects

Bir component mount edilirken ikinci parametre olarak verdiğimiz options object içine mocks ayarını vererek vuex için $store, vue router için $router ve $route, vue-i18n için $t gibi global nesneler mocklanabilir.

Örnek olarak personel listesinin olduğu bir sayfanın var olduğunu düşünelim. Her bir personel üzerine tıklandığında ilgili personelin detaylarının görüneceği bir sayfaya gittiğimizi farz edelim. Bu detay sayfasına giderken personel bilgisinin personelId isimli route parametresine göre başlık olarak `personelId numaralı personel`yazsın. Bu örneğe göre mock işlemimizi yapalım.

<template>
<h1 id="personel">
{{this.$route.params.personelId}} numaralı personel
</h1>
</template>
<script>
export default {
name: 'PersonelContainer',
};
</script>
import {mount} from "@vue/test-utils";
import Personel from "../../src/components/MockingGlobal/Personel";
describe("Mocking global objects", () => {
const factory = (options) => {
return mount(Personel,{
mocks: {
$route: {...options}
}
})
}
test("mocking route global object",() => {
const params = {
personelId: '32'
}
const wrapper = factory(params)
expect(wrapper.find('#personel').text())
.toBe(`${params.personelId} numaralı personel`)
})
})

Test dosyamızı incelediğimizde component mount edilirken mocks ayarının nasıl verildiğini ve içinde $route global nesnesinin ne şekilde mocklandığını görebiliyoruz.

Buna benzer olarak diğer global nesneler de mocklanabilir.

Triggering Events — User Input

Vue componentlerinde sıklıkla yapılan bir diğer iş ise kullanıcı ile etkileşime geçmek için kurulan form yapılarıdır. Bu bölümde user input olaylarının nasıl test edilebileceğine bakalım.

Örneğimizde bir login form olsun. Kullanıcı ismini ve parolasına girsin. Eğer parola ve kullanıcı ismi inputları dolu ise ‘welcome, kullanıcı’ gibi bir mesaj dönsün. Bu senaryoya göre işlemlerimizi yürütelim.

<template>
<div>
<form @submit.prevent="login">
<input v-model="username" data-username>
<input v-model="password" data-password>
<input type="submit">
</form>
<div
class="message"
v-if="submitted"
>
welcome, {{ username }}.
</div>
</div>
</template>
<script>
export default {
name: "UserInput",
data() {
return {
username: '',
password: '',
submitted: false
}
},
computed: {
checkInputs(){
return this.username.length > 0 && this.password.length > 0
}
},
methods: {
login() {
this.submitted = this.checkInputs
}
}
}
</script>

Şimdi bu component’e göre user input olaylarını gerçekleştirelim.

import { mount } from "@vue/test-utils";
import UserInput from "../../src/components/UserInput/UserInput";
describe("Simulating user input", () => {
const factory = () => {
return mount(UserInput)
}
test("should ",async () => {
const wrapper = factory()
await wrapper.find("[data-username]").setValue("atakan")
await wrapper.find("[data-password]").setValue("1234")
await wrapper.find("form").trigger("submit.prevent")
expect(wrapper.find(".message").text())
.toBe("welcome, atakan.")
})
})

Sırası ile aşağıdaki işlemleri yaparız.

  • username input’u bul ve içini doldur.
    - setValue kullanıcı girdisini simüle etmek için kullanılır.
  • password input’u bul ve içini doldur.
  • Sırada form’u submit etmek var. Bu olay trigger ile gerçekleşir.
    -trigger submit.prevent keydown.enter gibi modifiers kullanan evenler ile çalışır.
  • Son olarak mesajın doğru olarak verildiğinin kontrolü yapılır.

setValue ve trigger kendi iç yapılarında Vue.nextTick() döndürürler. nextTick DOM’un Vue Reactivity Sisteminin güncellenip güncellenmediğini kontrol eder. Buna bağlı olarak await setValue, await trigger kullanmamızın sebebi kısa yoldan nextTick olayını çözmek istememizden kaynaklanmaktadır. Bu şekilde Vue.nextTick() return edene kadar yani DOM güncellenene kadar beklemiş oluruz.

Asych olan bu yapı ile alakalı bu bilgileri buraya tıklayarak okuyabilirsiniz.

Ayrıca ilgili commit’e giderek örneği inceleyebilirsiniz.

Mocking a HTTP Client — Axios

Form yapıları girilen verileri bir endpoint’e submit etmek için kullanılır. Endpoint ile iletişime geçmek http kütüphaneleri kullanırız. Bu başlık altında Axios kütüphanesi kullanarak bir ajax request nasıl mocklanır öğreneceğiz.

İstek atacağımız API https://jsonplaceholder.typicode.com/ adresi olacak ve /posts/id endpoint’e istek atacağız.

Örnek olarak /posts/1 e istek atarsak dönen değer aşağıdaki gibi olur.

{
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}

İlk olarak Vue componentimizi oluşturalım.

<template>
<div>
<form @submit.prevent="getTodo">
<input type="number" v-model.number="todoId" data-id>
<input type="submit">
</form>
<div v-if="todo">
<h1 data-title>{{this.todo.title}}</h1>
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: "MockingAxios",
data() {
return {
todoId: '',
todo: null,
}
},
methods: {
async getTodo() {
try {
const response = await axios.get(`https://jsonplaceholder.typicode.com/posts/${this.todoId}`)
this.todo = response.data
} catch (e) {
console.log(e)
}
}
}
}
</script>

Component incelenecek olursa kullanıcıdan bir id girilmesi beklenir ve submit anında ise api ile iletişim sağlanır. Dönen değer todo datasına yazılır ve h1 etiketi içinde title alanı ekrana yazılır.

Şimdi testimizi adım adım oluşturmaya başlayalım.

İlk olarak sorulması gereken soru Axios HTTP Client nasıl mocklanır olmalıdır. Madem ki ben axios.get ile endpoint’e istek atacağım bunu mocklamam gerekmekte. Yapmam gereken ilk iş belli oldu.

  • Adım 1: axios library test dosyasına eklenir.
import axios from "axios";
  • Adım 2: axios dahil edildi sıradı bu modulün davranışının değiştirilmesi gerekliliği var. Çünkü ben gerçek bir istek atmak istemiyorum. Bunu ise jest.mock(…) bizim için sağlamakta. Bir modülü mocklamak için jest.mock() kullanılabilir.

get isteğini attığımda bana bir promise dönecek ve bunu çözerek gelen veriyi karşılamam gerekecek. Madem durum böyle işliyor kendi promise yapımı oluşturup buna göre resolve edebilirim.

jest.mock("axios",()=>({
get: (_url) => {
return new Promise((resolve) => {
resolve(true)
})
}
}))

Yukarıda bir axios modülünün mocklamasının reçetesini görüyoruz. İlk parametre hangi modülün mocklanacağı olur. Zaten bu modülü adım 1 de test dosyasına ekliyoruz. Bu demek oluyor ki ilgili component mount edildiğinde şayet içinde axios’a bağlı bir get işlemi yapılıyorsa mecburen bu süzgeçten geçecektir. Yani mocklanmış olacaktır.

Bu reçeteyi baz alarak geliştirmeyi devam ettirelim.

  • Adım 3: /posts/1 ve /posts/2 endpoinlerine istek attığımda hangi veriler dönüyor bunları Promise içinde belirt. Çünkü id:1 ve id:2 için farklı değerler döner. Bunları testin assertion kısmında kıyas olarak kullanacağız.
jest.mock("axios",()=>({
get: (_url) => {
return new Promise((resolve) => {
const responseData = [
{
"data": {
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}
},
{
"data": {
"userId": 1,
"id": 2,
"title": "qui est esse",
"body": "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla"
}
}
]
resolve(true)

})
}
  • Adım 4: Madem ki get isteği benim mock süzgecimden geçecek o zaman istek atılan adresin son kısmındaki id değerini alırım ve response data içindeki id ile kıyaslama yaparak resolve kısmında ona göre ilgili response’yi dönerim.
    Yani /posts/1 e istek atılmışsa 1'i yakalarım ve sonrasında responseData[0].data.id ile kıyaslarım ve ilk datayı resolve ederim. Buna göre de assertion yapabilirim.
jest.mock("axios",()=>({
get: (_url) => {
return new Promise((resolve) => {
const responseData = [
{
"data": {
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}
},
{
"data": {
"userId": 1,
"id": 2,
"title": "qui est esse",
"body": "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla"
}
}
]
const _id = _url.split("/").slice(-1)

if (parseInt(_id)===responseData[0].data.id){
resolve(responseData[0])
} else {
resolve(responseData[1])
}

})
}
}))
  • Adım 5: Assertion kısmında url kontrolü yapmak için son olarak global bir url değişkeni tanımlarım ve get(_url) içine gelen url’i bu global değişkene eşitlerim.
let url = ''
jest.mock("axios",()=>({
........
.......
const _id = _url.split("/").slice(-1)
url = _url
if (parseInt(_id)===responseData[0].data.id){
resolve(responseData[0])
} else {
resolve(responseData[1])
}
})
}
}))

Test dosyamızın tamamını ekleyerek test kontrollerimizi yapalım.

import { mount } from "@vue/test-utils"
import MockingAxios from "../../src/components/MockingAxios/MockingAxios";
import axios from "axios";
import flushPromises from "flush-promises"
let url = ''
const ids = {id1:'1',id2:'2'}
const expectedResult = [
{
"data": {
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}
},
{
"data": {
"userId": 1,
"id": 2,
"title": "qui est esse",
"body": "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla"
}
}
]
jest.mock("axios",()=>({
get: (_url) => {
return new Promise((resolve) => {
const responseData = [
{
"data": {
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}
},
{
"data": {
"userId": 1,
"id": 2,
"title": "qui est esse",
"body": "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla"
}
}
]
const _id = _url.split("/").slice(-1)
url = _url
if (parseInt(_id)===responseData[0].data.id){
resolve(responseData[0])
} else {
resolve(responseData[1])
}
})
}
}))
describe("Mocking a HTTP Client", () => {
const factory = () => {
return mount(MockingAxios)
}
test.each([[ids.id1, expectedResult[0].data.title],[ids.id2, expectedResult[1].data.title]])('Mocking Axios',
async (firstArgs, expectedResult) => {
const wrapper = factory()
await wrapper.find("[data-id]").setValue(firstArgs)
await wrapper.find("form").trigger("submit.prevent")
await flushPromises()
expect(url).toBe(`https://jsonplaceholder.typicode.com/posts/${firstArgs}`)
expect(wrapper.find("[data-title]").text()).toEqual(expectedResult)
});
})

Yukarda test dosyamızın tamamı görünmekte. Bu testte test.each kullanılmaktadır. Amacı farklı durumları içeren testleri tek bir noktadan test etmek için bize kolaylık sağlamasıdır. Daha detaylıca kontrol etmek isterseniz takip eden linke tıklayabilirsiniz.[test.each]

İlk olarak globalde 1 ve 2 olarak id’ler tanımlanır.

const ids = {id1:'1',id2:'2'}

Sonrasında bunlara özel postlar apiden alındığında dönecek olan değerler expect kısmında iddia edilebilmesi için expectedResult isimli dizi içine tanımlanmıştır.

Geriye sadece input’a giriş yapıp submit eventini triggerlamak kalıyor. Trigger işleminden sonra yazılan flushPromises() ise durumu pending olan tüm promiseleri anında çözer ve DOM update olur.

İlk assertion işlemimiz doğru adrese istek atılıp atılmadığını test eder.

expect(url).toBe(`https://jsonplaceholder.typicode.com/posts/${firstArgs}`)

İkinci assert işleminde ise istek doğru döndü ise render edilen title karşılaştırma yapılarak iddia işlemi sona erdirilir.

--

--