一個做 Unit test 可能會用到的方法

做作業時學到的,所以想記一下。

簡述 SUT 與 DOC

這邊先介紹兩個專有名詞:

  • SUT(System Under Test)
  • DOC(Depended-on Component)

簡單來說 SUT 可以想成是真正要測試的「目標」,而 DOC 則是用來「協助目標」的其他東西。

這樣講可能有點抽象,所以直接來舉個例子:

1
2
3
4
5
6
function forEach(array, callback) {
for (const item of array) {
callback(item)
}
}
module.exports = forEach

就是一個簡單的 forEach

這時候我們可能會想測試 forEach 的運作是否正常,不過它顯然需要一個 callback 來輔助執行。這時候就可以把 forEach 當作是 SUT,而 callback 則是 DOC。

為什麼要這樣分?

因為我們其實真正想測試的是 forEach,不是 callback,但如果 callback 出了一點問題可能就會連帶影響到 forEach 的結果,這樣不是很好。

所以才會利用這種方式來切割出「最小單位」,讓 A 跟 B 之間不要互相影響。

mock function 替身函式

前面的舖陳就是為了介紹「Mock」這個東西。

Mock 的中文翻作「模擬、模仿」,在 Unit test 裡面我們會把 Mock 想成是一種「替身函式」。藉由這個替身函式來協助我們真正想測試的 function。

以上面 forEach 的例子來說,我們就會生一個 Mock 出來,當作 callback 的替身。

這邊不會講太深,只會紀錄一下我目前學到的用法,想知道更多可以參考 官方文件

總之呢,在實際應用以前,先介紹一下在 jest 裡使用 Mock 的基本用法。

開啟方式

jest.fn()

1
2
3
4
5
6
it('should return undefined', () => {
// 建立一個 Mock
const mockFunction = jest.fn()
// 預設的回傳值為 undefined,就跟 call 一般的 function 一樣
expect(mockFunction()).toBe(undefined)
})

指定回傳值

  • mockReturnValueOnce 一次
  • mockReturnValue 永遠
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
it('should return true only in first call', () => {
const mockFunction = jest.fn()
// 只有第一次 call 會回傳 true
mockFunction.mockReturnValueOnce(true)
expect(mockFunction()).toBe(true)
expect(mockFunction()).toBe(undefined)
expect(mockFunction()).toBe(undefined)
expect(mockFunction()).toBe(undefined)
})
it('should always return true', () => {
const mockFunction = jest.fn()
// 只有第一次 call 會回傳 true
mockFunction.mockReturnValue(true)
expect(mockFunction()).toBe(true)
expect(mockFunction()).toBe(true)
expect(mockFunction()).toBe(true)
expect(mockFunction()).toBe(true)
})
it('should return true, false, false and true', () => {
const mockFunction = jest.fn()
// 第一次回傳 true
// 第二次回傳 false
// 第三次回傳 false
// 第四次回傳 true
mockFunction
.mockReturnValueOnce(true)
.mockReturnValueOnce(false)
.mockReturnValueOnce(false)
.mockReturnValueOnce(true)

expect(mockFunction()).toBe(true)
expect(mockFunction()).toBe(false)
expect(mockFunction()).toBe(false)
expect(mockFunction()).toBe(true)
})

Mock 的實際應用

檢查 Mock 被 call 了幾次

mockFunction.mock.calls.length

1
2
3
4
5
6
7
it('mock function should be call two times', () => {
// 把這個 mock 當成 callback
const mockFunction = jest.fn((x) => x + 1)
forEach([1, 2, 3], mockFunction)
// mock 應該要被執行三次
expect(mockFunction.mock.calls.length).toBe(3)
})

檢查 Mock 是怎麼被呼叫的

toHaveBeenCalledWith

1
2
3
4
5
6
7
it('mock function should be call with an argument', () => {
const mockFunction = jest.fn((x) => x + 1)
forEach(['A'], mockFunction)
// 可以想成是這樣:mock.call(this, 'A')
// 就是 mock 是怎麼被呼叫的意思
expect(mockFunction).toHaveBeenCalledWith('A')
})

檢查 Mock 每一次是怎麼被呼叫的

toHaveBeenNthCalledWith

1
2
3
4
5
6
7
8
it('mock function should be call with each element', () => {
const mockFunction = jest.fn((x) => x + 1)
forEach(['A', 'B', 'C'], mockFunction)
// mock 的每一次是怎麼被 call 的
expect(mockFunction).toHaveBeenNthCalledWith(1, 'A')
expect(mockFunction).toHaveBeenNthCalledWith(2, 'B')
expect(mockFunction).toHaveBeenNthCalledWith(3, 'C')
})

檢查 Mock 被呼叫時的參數資訊

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
it('mock function should be call with each element', () => {
const mockFunction = jest.fn()
const array = [
{ name: 'PeaNu', age: 20 },
{ name: 'PPB', age: 18 },
{ name: 'ET', age: 100 }
]
forEach(array, mockFunction)

// calls[0]: 第一次執行的參數資料
// calls[0][0]: 第一次執行的第一個參數
// calls[0][0]['name']: 第一次執行的第一個參數的 name 屬性
expect(mockFunction.mock.calls[0][0]['name']).toBe('PeaNu')
expect(mockFunction.mock.calls[0][0]['age']).toBe(20)

expect(mockFunction.mock.calls[1][0]['name']).toBe('PPB')
expect(mockFunction.mock.calls[1][0]['age']).toBe(18)

expect(mockFunction.mock.calls[2][0]['name']).toBe('ET')
expect(mockFunction.mock.calls[2][0]['age']).toBe(100)
})
mentor-program-day99 mentor-program-day98
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×