เวลาที่เราพัฒนาเว็บไซต์ด้วย React หรือ NextJS นั้น เป็นเรื่องปกติที่ต้องมีการประกาศฟังก์ชั่นบางอย่างภายใน component ไม่ว่าจะเป็น ฟังก์ชั่นที่เราสร้างขึ้นมาเอง เพื่อทำงานบางอย่าง หรือ ฟังก์ชั่น hooks ต่างๆ ที่ library มีใช้เรียกใช้งาน
หน้าตามันก็จะประมาณนี้
import { useCallback, useState } from 'react'
const Counter = () => {
const [count, setCount] = useState(0)
const increment = useCallback(() => setCount((x) => x + 1), [])
const decrement = useCallback(() => setCount((x) => x - 1), [])
return (
<div>
<h1>Count: {count}</h1>
<button id='incrementButton' onClick={increment}>
-
</button>
<button id='decrementButton' onClick={decrement}>
+
</button>
</div>
)
}
export default Counter
counter.tsx
ซึ่งถ้าหากว่าเราต้องการทดสอบพฤติกรรมของ component นี้ โดยใช้ Cypress ก็สามารถทำได้ โดยง่าย
import React from 'react'
import Counter from './counter'
describe('<Counter />', () => {
it('renders', () => {
cy.mount(<Counter />)
cy.get('h1').contains('Count')
})
})
counter.cy.tsx
หรือถ้าหากอยากที่จะทดสอบพฤติกรรมการทำงานของ component นี้ก็สามารถทำได้ด้วยเช่นกัน
import React from 'react'
import Counter from './counter'
describe('<Counter />', () => {
it('renders', () => {
cy.mount(<Counter />)
cy.get('h1').contains('Count: 0')
cy.get('#incrementButton').click()
cy.get('h1').contains('Count: 1')
})
})
counter.cy.tsx
แล้วถ้าหากว่าเราอยากจะทดสอบการทำงานเฉพาะ custom hook หละ จะทำยังไง... หลังจากที่ลองทำดูก็พบวิธีการทดสอบ
Custom Hook
ซึ่งอย่างที่รู้กัน (มั้งนะ) ว่าเราสามารถที่จะแยกเอาส่วน hooks ของ library และฟังก์ชั่นต่างๆ ที่เราเขียนไว้ แยกออกมาเป็น custom hook อีกตัวหนึ่งได้ เพื่อให้ง่ายต่อการใช้งานและปรับปรุงแก้ไขในอนาคต (และทดสอบ)
import { useCallback, useState } from "react"
const useCounter = () => {
const [count, setCount] = useState(0)
const increment = useCallback(() => setCount((x) => x + 1), [])
const decrement = useCallback(() => setCount((x) => x - 1), [])
return {
count,
increment,
decrement
}
}
export default useCounter
useCounter.tsx
import useCounter from "./useCounter"
const Counter = () => {
const { count, increment, decrement } = useCounter()
return (
<div>
<h1>Count: {count}</h1>
<button onClick={decrement}>-</button>
<button onClick={increment}>+</button>
</div>
);
}
export default Counter
counter.tsx
เท่านี้เราก็สามารถเริ่มทดสอบ ฟังก์ชั่นต่างๆ ภายใน useCounter ได้แล้ว
ทดสอบ Custom Hook ผ่าน Component
โดยปกติแล้ว ถ้าหากเราต้องการทดสอบ hook เราจะต้องทำสอบผ่านทาง component แบบนี้
import React from 'react'
import Counter from './counter'
import useCounter, { UseCounterReturnType } from './useCounter'
describe('<Counter />', () => {
it('renders', () => {
cy.mount(<Counter />)
cy.get('h1').contains('Count: 0')
cy.get('#incrementButton').click()
cy.get('h1').contains('Count: 1')
})
})
counter.cy.tsx
แล้วถ้าเราอยากทดสอบ custom hook โดยไม่ผ่าน component จริงๆ ที่เรียกมใช้งาน ก็สามารถทำได้โดยการ Mock Component ขึ้นมาเพื่อ เรียกใช้งาน hooks ที่ต้องการทดสอบ
import React from 'react'
import Counter from './counter'
import useCounter, { UseCounterReturnType } from './useCounter'
describe('<Counter />', () => {
it('renders only useCounter', () => {
// Arrange
let counter: UseCounterReturnType
const MockComponent = () => {
counter = useCounter()
return null
}
// Act
cy.mount(<MockComponent />).then(() => {
const count = counter.count
// Assert
expect(count).to.equal(0)
})
})
})
counter.cy.tsx
การทดสอบด้วยวิธีนี้ จะต่างจากวิธีแรก ตรงที่ ใน mock component จะ return ค่ากลับเป็น null แทน html และต้องสร้างตัวแปรขึ้นมาเพื่อรับค่าจาก hooks (useCounter) ด้วย
อีกอย่างที่แตกต่างกันคือ การ mount และ assert ค่า จะถูกเปลี่ยนเป็น assert ใน then แทน