iT邦幫忙

2022 iThome 鐵人賽

DAY 22
0
Modern Web

Fastify 101系列 第 22

[Fastify] Day22 - Testcontainers

  • 分享至 

  • xImage
  •  

大家好,我是 Yubin

這篇文章會跟大家介紹什麼是 Testcontainers,以及使用 vitest 這個測試框架實際整合 testcontainers 來跑測試。


Testcontainers

我們的應用程式常常需要其他服務的串接,比如說,資料庫、快取伺服器、認證伺服器等許多外部資源。

但在寫測試的時候,通常會將各個 function 拉出來寫,或是 mock 掉外部的服務,雖然可以測到一個個小 function 的功能,但常常會疏忽掉各個資源在串接的時候,那些預期之外的問題。

所以許多團隊會在執行測試的時候,會真的去串接外部的服務,只是那個服務不是 for production 用的,而是專門用來測試的服務。

但維護那些"專門用來測試"的外部服務,成本很大,除了伺服器成本也有龐大的人力成本。

如果,在執行測試之前,可以自動開啟一個 container,當作我們相依的那些外部服務,是不是就可以放心的使用那些服務來做更全面的測試。

而且藉由 container 的特性,這些服務的狀態都是可控的、每個容器彼此隔離、啟動速度快、用完即丟。

尤其適合在 CI 時期跑測試,也不會增加太多伺服器資源或管理上的負擔。


Testcontainers 提供了許多方法可以讓我們使用程式去控制 Container。

Testcontainers 是 JUnit 起家的,但現在官方團隊也維護著多種不同語言的實作。

安裝 Testcontainers:

npm i -D testcontainers

可以利用 testcontainers 中的 GenericContainer 帶入想要的 image,接著可以設定環境變數或要開到本機的 port,最後執行 .start(),就可以把一個 container 跑起來。程式就可以跟那個 container 提供的服務進行互動。

import { GenericContainer } from 'testcontainers'

const container = await new GenericContainer("mongo")
                            .withExposedPorts(27017)
                            .start()

更多設定及範例可以參考官方 GitHub

但是直接操作 testcontainers 有點麻煩每次寫測試都要打一堆字,所以我包了一個套件,來方便做 mongoose 連接 MongoDB 的測試。


Testcontainers-mongoose

testcontainers-mongoose 封裝了一些方便的函式,讓我們可以輕鬆的在整合了 mongoose 的專案中,使用 testcontainers 的特性來協助我們進行測試。

安裝 testcontaienrs-mongoose:

npm i -D testcontainers-mongoose

如果你的測試想要函蓋到資料庫的串接,例如一個 request 進來,經過 route,執行某個 service,透過某個 repo 跟資料庫進行互動。

想測到這整段的執行結果是不是符合預期,那使用 testcontaienrs-mongoose 幫你起一個 "測試用的" mongo container 是非常便利的。

import * as dbHandler from 'testcontainers-mongoose'

describe('testcontainers-mongoose test', () => {

  beforeAll(async () => {
    await dbHandler.connect('harbor.yourcompany.com/mongo:4.4.4')
    // ...
  })
  afterAll(async () => {
    await dbHandler.closeDatabase()
  })

  afterEach(async () => {
    await dbHandler.clearDatabase()
  })

  it('some test using mongoose', async () => {
    // ...
  })

})

上述範例中,我們透過 beforeAll hook 來連接資料庫,他會根據指定的 image,將 container 跑起來,並把 mongoose 接上那個 container 提供的服務。

每一個測試案例執行完,資料庫裡可能被該測試所更改到,為了避免影響下一個測試案例的執行,透過 afterEach hook,在每一個測試案例執行完後,都把資料庫清空。
如此一來,可以確保每個測試案例的執行,都有一樣的資料庫狀態。(就是全空,乾淨的資料庫)

最後跑完所有測試後,在 afterAll hook 中關閉資料庫的連線,該函式也會將 mongo container 關閉。

Vitest

Vitest 是一個 JavaScript 的測試框架,算是 Jest 的替代方案。
因為用起來比 Jest 順手太多了,所以這篇使用 Vitest 來帶個範例。

安裝 Vitest

npm i -D vitest

對,就這樣。
不像 Jest 還要安裝 jest, @types/jest, 可能還需要 ts-jest

設定 vitest.config.ts

新增 vitest.config.ts

import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {}
})

基本上也不用額外的設定,其他設定選項可以參考 vitest: config

測試案例

假設要測的東西是,
如果資料庫有一筆資料,我打 GET /cats 的 Endpoint,會拿到一筆記錄。

新增 cat.spec.ts

import { FastifyInstance } from 'fastify'
import { startServer } from '../server'
import { describe, beforeAll, expect, test, afterAll, afterEach } from 'vitest'
import { CatRepoImpl } from '../repos/cat-repo'
import * as dbHelper from 'testcontainers-mongoose'

describe('Server test', () => {
  let server: FastifyInstance

  beforeAll(async () => {
    await dbHelper.connect('mongo')
    server = startServer(8888)
    await server.ready()
  })

  afterEach(async () => {
    await dbHelper.clearDatabase()
  })

  afterAll(async () => {
    await dbHelper.closeDatabase()
  })

  test('Given a cat in db, when GET /cats, then get one record', async () => {
    // arrange
    const cat = {
      name: 'Fat Orange',
      weight: 7
    }
    await CatRepoImpl.of().addCat(cat)

    // act
    const response = await server.inject({
      method: 'GET',
      url: '/cats'
    })
    const cats = JSON.parse(response.body)['cats']

    // assert
    expect(cats).toHaveLength(1)
  })
})

與 Jest 不同,預設狀態下 vitest 的每個物件都需要透過 import 的方式使用。

我們在 beforeAll hook,把 testcontaienrs 打起來,並接上 mongoose。

然後 test() 測試案例中,定義了標準的 AAA Pattern。

Arrange 階段,在資料庫中新增一筆 Cat 的資料。

Act 階段,實際發送一個 GET 的 Request 到 /cats Endpoint。

Assert 階段,檢查結果是否符合預期。(此處要測試的是,在資料庫中只有一筆紀錄的情況下,回傳的資料比數是不是也是一筆)

要執行這個測試,只需要在 package.json 中的 scripts 物件中定義:

"test": "vitest"

接著在 terminal 中執行 npm run testnpm t 就可以。


文中沒有提到實作 route repo 的那些細節,完整程式可以參考 GitHub 上的完整範例

藉由 testcontainers,讓我們在測試時期透過 containers 的支援,可以簡單無負擔的進行更高階層的測試。

推薦大家使用。


上一篇
[Fastify] Day21 - Cache with Redis
下一篇
[Fastify] Day23 - Upload File
系列文
Fastify 10130
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言