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

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.toml

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 安全型別驗證就能夠減少用 expect.

By GXiang
把 MixerBox 歌單匯入到 Spotify

把 MixerBox 歌單匯入到 Spotify

本文由 個人medium文章 同步 筆者最近從 MixerBox 跳槽到 Spotify,但因為 Spotify 推薦的歌單不符合我的口味,又想把 MixerBox 歌單直接無痛移轉到 Spotify,網路上爬文許久,雖然有看到 Jerry 大大分享的這篇文章,但似乎已經沒辦法使用,既然沒有現成的解決方案,那我們就只好動動自己的小腦袋了🧠 貼心體醒:接下來的內容並不會要求撰寫/查看任何程式🙂 使用工具:瀏覽器, Visual Studio Code 筆者在目前看到的平台歌單轉移工具 TuneMyMusic 中,我發現了一個可能性,在第一步選擇歌曲來源時,有一個選項叫做「任意文本」。 點了之後才發現,原來可以利用歌名加上換行分隔作為清單進行匯入!那事情就簡單多了,我們只要能拿到自己播放清單中的歌曲文字列表就可以直接匯入。 接下來我們來到 MixerBox Web 版頁面,點擊「我的音樂庫」,可以看到自己的歌單。 點擊你想要的歌單後,所有歌曲都會呈現在頁面上。 那麼接下來的問題是:

By GXiang
問題解決 child_process execSync /bin/sh: pnpm: command not found

問題解決 child_process execSync /bin/sh: pnpm: command not found

Context 我在執行 child_process.execSync 時,使用了下方程式碼去跑 db migration 並且指定了環境變數 DATABASE_URL 去執行 schema refresh & seed execSync('pnpm -F backend db:reset && pnpm -F backend seed', { env: { DATABASE_URL: databaseUrl }, }) 本人是使用 Windows 作業系統,這行指令在我電腦中執行的很順暢,所以就開開心心的把這行程式推上 Github 了。 不料沒多久,一位使用 Mac 的同事就對我說這行程式沒辦法跑啊,一直寫說 pnpm command not

By GXiang