เขียน Unit Test ด้วย React + TypeScript + Vitest

React May 28, 2023

ปกติโปรเจ็ค React ส่วนใหญ่จะนิยมใช้ Jest + Testing Library กันใช่มั้ย แต่ช่วงนี้ผมได้ลองใช้ Vitest แล้วรู้สึกชอบมากกว่า ก็เลยพยายามเปลี่ยนมาใช้ Vitest (เอามาแทนที่ Jest) วันนี้เลยมาเขียนบล็อกแนะนำการ ติดตั้ง การตั้งค่า Vitest สำหรับโปรเจ็ค React.js กันครับ

โดยตัวอย่างโปรเจ็ค ผมจะใช้เป็น default template นะครับ

  • React + TypeScript ด้วยการใช้ Vite
  • Vitest
Vite
Next Generation Frontend Tooling
Vitest
A blazing fast unit test framework powered by Vite

สร้างโปรเจ็ค

เริ่มต้น ผมสร้าง default template โดยการใช้ Vite (จริงๆ Vitest ใช้ได้เกือบทุก library/framework นะครับ)

npm create vite@latest hello-vitest -- --template react-ts

เราก็จะได้ Project React + Vite มาแบบง่ายๆ 1 เว็บ

ติดตั้ง Vitest

การติดตั้ง Vitest คือ

npm install -D vitest

การ Config Vitest

โดยปกติ ถ้าโปรเจ็คเราเป็น Vite ตัว Vitest จะอ่าน config จาก vite.config.ts ได้เลย

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  test: {
    globals: true,
  },
})

แต่ว่าการ config vitest ในไฟล์ config ของ vite นั้น ตัว TypeScript มันไม่รู้จัก เราต้องบอกให้มัน reference ไปที่ Vitest Type ครับ คือเพิ่ม reference type ที่ส่วน top (ใช้ triple slash) จะได้เป็นแบบนี้

/// <reference types="vitest" />
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  test: {
    globals: true,
  },
})

หรืออีกตัวเลือกคือ ไม่ใช้ triple slash แต่เราก็เปลี่ยน defineConfig จาก vitest/config แทนที่ vite แบบนี้

-import { defineConfig } from 'vite'
+import { defineConfig } from 'vitest/config'

ส่วนที่ผมกำหนด globals: true คือ ทำให้เราสามารถใช้ describe, it test ได้แบบ global เหมือนกับ Jest (ข้อดีคือเหมาะสำหรับคนที่ migrate จาก Jest ไม่ต้องแก้อะไรมาก)

แต่ถ้าใช้ global เป็น false (default) เวลาเขียนเทส ก็จะ import จาก vitest ไม่ใช่ global แล้ว แบบนี้

import { describe, it, expect } from 'vitest'

describe('something', () => {
  it('should work'), () => {
     expect(true).toEqual(true)
  })
})

ทำการเพิ่ม Compiler Options ที่ไฟล์ tsconfig.json เพื่อให้ TypeScript รู้จัก Vitest globals (ไม่งั้นตัว VS Code อาจจะฟ้อง error)

{
  "compilerOptions": {
    "types": ["vitest/globals"]
  }
}

การ Config ด้วย vitest.config.ts

นอกจากการ Config ผ่าน Vite Config แล้ว เราสามารถ config ด้วย Vitest เอง คือไฟล์ vitest.config.ts การ config แบบนี้ เหมาะสำหรับโปรเจ็คที่เราไม่ได้ใช้ Vite นั่นเอง

  • ตัว vitest.config.ts จะเป็นมี priority สูงกว่า คือถ้ามีไฟล์ vitest กับ vite ตัว config จะอ่าน vitest.config.ts ก่อน อ่านไฟล์ vite.config.ts
  • ตัวอย่าง config (เหมือนกับ vite.config.ts)
import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    // ...
  },
})

เพิ่ม script test ใน package.json

{
  "scripts": {
    "test": "vitest"
  }
}

เขียนเทสแรก

ลองเขียน test ขึ้นมาไฟล์นึง สมมติ ผมจะทำ helpers function ซักตัว เกี่ยวกับการ calculate ละกัน ผมตั้งชื่อว่า calculate.spec.ts ในโฟลเดอร์ src/helpers

import { add } from './calculate'

describe('add', () => {
  it('should add two numbers', () => {
    expect(add(1, 2)).toBe(3)
  })
})

และไฟล์ calculate.ts แบบนี้

export const add = (a: number, b: number): number => a + b
ปกติ default ถ้าเราไม่ได้ config อะไร ตัว vitest จะ scan หาไฟล์ *.spec หรือ *.test และรันเทสให้เราครับ

ลองรันเทสดู

npm test

จะได้ผลลัพธ์ประมาณนี้

✓ src/helpers/calculate.spec.ts (1)

 Test Files  1 passed (1)
      Tests  1 passed (1)
   Start at  15:40:14
   Duration  11ms

ตัว vitest จะเข้าโหมด watch ให้เราเอง เวลาเราแก้ไขไฟล์ ตัว test ก็จะทำการ re-run ใหม่ครับ สะดวกมาก (กด q เพื่อออกจากโหมดเทส)

Test Coverage

ตัว default coverage ของ Vitest คือ v8 ถ้าเราไม่ได้ config ว่าจะให้เป็น reporter provider อะไร แต่ถ้าเราจะใช้ coverage ยังไงก็ต้องติดตั้ง package ก่อน

npm install -D @vitest/coverage-v8
(กรณีไม่ได้ติดตั้ง แล้วเราสั่งรันเทสด้วย coverage ตัว Vitest จะ detect และถามเราว่า ต้องการติดตั้ง coverage-v8 หรือไม่ ก็เลือกติดตั้งวิธีนี้ก็ได้เช่นกัน)

ทีนี้เวลาเราจะเทสด้วย coverage ก็ใส่ option ลงไป แบบนี้

vitest --coverage

Vitest UI

ติดตั้ง Vitest UI เพื่อให้เราสามารถรันโหมด UI ได้ ข้อดีคือ ดูง่าย สามารถดูเทส รันเทส ผ่าน Browser ได้เลย

npm install -D @vitest/ui

จากนั้นเพิ่ม script สำหรับ ui ต่อจาก coverage แบบนี้

{
  "scripts": {
    "test": "vitest",
    "test:coverage": "vitest --coverage",
    "test:ui": "vitest --ui",
  }
}

ผลลัพธ์เมื่อรัน coverage

 Test Files  1 passed (1)
      Tests  1 passed (1)
   Start at  14:08:25
   Duration  189ms (transform 21ms, setup 0ms, collect 9ms, tests 2ms, environment 0ms, prepare 58ms)

 % Coverage report from v8
--------------|---------|----------|---------|---------|-------------------
File          | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
--------------|---------|----------|---------|---------|-------------------
All files     |     100 |      100 |     100 |     100 |                   
 calculate.ts |     100 |      100 |     100 |     100 |                   
--------------|---------|----------|---------|---------|-------------------

ผลลัพธ์เมื่อรัน vitest ui

สรุป

บทความนี้ก็เป็นวิธีการตั้งค่า Vitest แบบง่ายๆนะครับ ลองทดลองเล่นกันดู เขียนเทสเพิ่ม ลองรัน รวมถึง ตัว environment default คือ node.js เราสามารถเทสด้วย jsdom หรือ happy-dom ได้ เพื่อให้ environment เป็น web ครับ ลองดูนะครับ เดี๋ยวบทความหน้าจะเขียนเรื่องการเทสตัว React Component กันครับ

Happy Coding ❤️


Reference

Vitest
A blazing fast unit test framework powered by Vite

Tags

Chai Phonbopit

เป็น Web Dev ทำงานมา 10 ปีหน่อยๆ ด้วยภาษา JavaScript, Node.js, React, Vue และปัจจุบันกำลังสนใจ Web3, Crypto และ Blockchain เขียนบล็อกที่ https://devahoy.com