Jest, Testcontainers 如何不重新建立 container 情況下執行 e2e 測試?

分享
Jest, Testcontainers 如何不重新建立 container 情況下執行 e2e 測試?
Photo by Ferenc Almasi / Unsplash

前言

前不久才研究 Testcontainers 如何套用在 jest e2e 測試環節中,不過這方面進展不錯,目前在每次執行 e2e 測試時都能啟動新的 MySQL 容器並且套用 Primsa 的 migration,用起來真香啊。

但是仍存在一個令我困擾的問題,就是在每一次跑測試時都會 Recreate 一次 Testcontainers,我在想可不可以像過往一樣就把 DB Container 開著,在跑測試時直接用就好。

解決方案

Testcontainers Node.js 官方文件提到:

Enabling container re-use means that Testcontainers will not start a new container if a Testcontainers managed container with the same configuration is already running.

This is useful for example if you want to share a container across tests without global set up.
const container1 = await new GenericContainer("alpine")
 .withCommand(["sleep", "infinity"])
 .withReuse()
 .start();

const container2 = await new GenericContainer("alpine")
 .withCommand(["sleep", "infinity"])
 .withReuse()
 .start();

expect(container1.getId()).toBe(container2.getId());
Container re-use can be enabled or disabled globally by setting the TESTCONTAINERS_REUSE_ENABLE environment variable to true or false. If this environment variable is not declared, the feature is enabled by default.

而在 Jest globalSetup 階段,以 MySQL 為例,我們可以透過 withReuse方法複用 Container。

import { Config } from '@jest/types'
import { MySqlContainer } from '@testcontainers/mysql'
import { Logger } from '@nestjs/common'


export default async function (
    globalConfig: Config.GlobalConfig,
    projectConfig: Config.ProjectConfig,
) {
  Logger.log('starting test db...')
  const mysqlContainer = await new MySqlContainer('mysql:8.4')
      .withUsername('user')
      .withUserPassword('password')
      .withDatabase('pc')
      .withExposedPorts({
          container: 3306,
          host: 3307,
      })
      .withReuse()
      .start()
  globalThis.mysqlContainer = mysqlContainer
  // ...
}

setup.ts

💡
在 Jest 環境下,要達成後續每次執行測試時都 Reuse,在 globalTeardown 階段就不要把 started 的 Container 關閉掉。
否則後續執行測試時,就會找不到已啟動的 Container 去做複用,到時候又得建立一個新的 Container,這樣就本末倒置了😂。

您可以設定好 Jest 環境變數,搭配下方條件判斷式來決定是否要關閉已啟動的 Container

import { Config } from '@jest/types'
import { Logger } from '@nestjs/common'

export default async function (
    globalConfig: Config.GlobalConfig,
    projectConfig: Config.ProjectConfig,
) {
    await closeApp()
    if (projectConfig.globals.CLOSE_TEST_DB) {
        await closeDb()
    }
}

async function closeApp() {
    Logger.log('closing app...')
    await globalThis.app.close()
    Logger.log('app closed...')
}

async function closeDb() {
    Logger.log('stopping db...')
    await globalThis.mysqlContainer.stop()
    Logger.log('db stopped...')
}

teardown.ts

{
  // ...
  "globalSetup": "<rootDir>/test/setup.ts",
  "globalTeardown": "<rootDir>/test/teardown.ts",
  "globals": {
    "CLOSE_TEST_DB": false
  }
}

jest-e2e.json

效能比較

測試環境

  • OS: Windows 11
  • CPU: 11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz
  • Disk: 美光 Crucial P2 1000GB NVMe
  • RAM: 24GB (16+8) DDR4-3200
  • Docker Image: mysql:8.4

沒有 Reuse

without reuse

有 Reuse

reuse
💡
在使用 reuse 的狀態下,後續每次執行 e2e 測試時,都能夠為我節省數十秒的時間。

Reuse Testcontainers 在使用上仍需注意幾點:

  1. 在 e2e 測試環境下為了確保測試獨立性,需要確保每次執行的測資、DB Schema 都是乾淨、設定好的。
  2. 後續若沒有使用到 Testcontainers 時,可以手動將該容器關閉掉,以釋放空間。

Read more

Windows Sandbox 沙箱閃退顯示連線已中斷解決方法

Windows Sandbox 沙箱閃退顯示連線已中斷解決方法

想拿 Windows Sandbox 測試東西,隔離環境用完就丟最省事。結果一打開沙箱就直接跳「與 Windows 沙盒環境的連線已中斷。是否要重新連線?」,訊息一出來就閃退,連畫面都還沒進去。 翻到 Eleven Forum 這串,有大神直接講解怎麼解決:把 vGPU 共用關掉。 Just had this issue after using Sandbox for years. My fix, as found in an Arabic YouTube video, is to use gpedit.msc and change the Computer Configuration -> Administrative

By GXiang
關於 Firefox 複製網址列連結貼上文本編輯器沒有網頁 Title 的解決方法

關於 Firefox 複製網址列連結貼上文本編輯器沒有網頁 Title 的解決方法

最近從 Edge 換回 Firefox,用沒幾天就被一個小地方卡住。 在 Edge 上,網址列複製網址、到 AFFiNE 筆記裡貼上,出來的是一條帶網頁標題的超連結,標題直接寫在筆記裡,之後回頭看一眼就知道那是什麼。Firefox 沒有這個行為,貼上去就是一長串網址: 筆記一多,這些網址就是一坨要一條條點開才知道是什麼的東西,關鍵字也搜不到。 先去看官方有沒有打算做。Mozilla Connect 上有這則提案:When copy / pasting URL include Meta Title of the ... - Mozilla Connect,截至寫這篇為止沒看到後續進度。 那就找插件。試了兩個: URL2Clipboard 功能最接近我要的,而且它有個細節做得很好:Ctrl + V 貼出來是帶超連結的標題,Ctrl + Shift + V(貼上為純文字)則只有標題文字,

By GXiang
UV python 操作筆記

UV python 操作筆記

前陣子看到了 uv 這個 python 套件、專案管理器,使用了之後發現簡直快、輕便又好上手,下方是我常使用的指令集 (持續更新中): 查看 python 版本清單 uv python list 💡已安裝的旁邊會顯示 python 安裝目錄位址 安裝 python 版本 # latest uv python install # specific version uv python install <version number> 初始化 uv 專案資料夾。 uv init <project name> 💡新專案預設會自動產生 git repository、.python-version、pyproject.

By GXiang
ts-rest 的 API 測試手法(1)

ts-rest 的 API 測試手法(1)

今天與朋友討論到 ts-rest 撰寫 API test 的時候可以用 Zod 的 safeParse().success 判斷 API Response 的 schema 正不正確。 Sample Code: const { body, status } = client.users.create({ body: createUserPayload(), }) expect( userContract.create.responses[201].safeParse(body) .success, ).toBe(true) 這樣做確實會減少許多傳統需要手動添加 expect.any(Type)...等等的型別驗證,如果只是單純驗證型別,確實透過 ts-rest 合約搭配的 Zod 的 safeParse

By GXiang