Skip to Content
Testcontainers ガイドNode.js で Testcontainers を使い始める

Node.js で Testcontainers を使い始める



コードを入手する

Testcontainers は、Docker コンテナでラップされた実際のサービスを使用して統合テストを簡単かつ軽量にセットアップするためのテストライブラリです。Testcontainers を使用することで、モックやインメモリサービスを使わずに、本番環境で使用するのと同じ種類のサービスとやり取りするテストを記述できます。

Testcontainers を初めて使用する場合は、Testcontainers とは何か、なぜ使うべきなのか?を読んで、Testcontainersについて詳しく学んでください。

Testcontainers を使用して PostgreSQL データベースを用いた Node.js アプリケーションをどのようにテストできるかを見てみましょう。

新しいプロジェクトの作成

以下のコマンドを使用して、新しい Node.js プロジェクトを初期化します:

npm init -y

依存関係の追加

以下のコマンドで pgjest、および @testcontainers/postgresql を依存関係として追加します:

npm install pg --save npm install jest @testcontainers/postgresql --save-dev

テストとソリューションの作成

PostgreSQL データベースに顧客情報を保存し、取得するシンプルなアプリケーションを作成しましょう。

テスト駆動開発 (TDD) はソフトウェアを開発するうえで優れた方法であり、Testcontainers を組み合わせることで、信頼を持って迅速に反復作業を進め、動作するソリューションを得ることができます。それでは、PostgreSQL に顧客を保存し、返す最初のテストを書いてみましょう。

const { Client } = require("pg"); const { PostgreSqlContainer } = require("@testcontainers/postgresql"); const { createCustomerTable, createCustomer, getCustomers } = require("./customer-repository"); describe("Customer Repository", () => { jest.setTimeout(60000); let postgresContainer; let postgresClient; beforeAll(async () => { postgresContainer = await new PostgreSqlContainer().start(); postgresClient = new Client({ connectionString: postgresContainer.getConnectionUri() }); await postgresClient.connect(); await createCustomerTable(postgresClient) }); afterAll(async () => { await postgresClient.end(); await postgresContainer.stop(); }); it("should create and return multiple customers", async () => { const customer1 = { id: 1, name: "John Doe" }; const customer2 = { id: 2, name: "Jane Doe" }; await createCustomer(postgresClient, customer1); await createCustomer(postgresClient, customer2); const customers = await getCustomers(postgresClient); expect(customers).toEqual([customer1, customer2]); }); });

beforeAll ブロックでは、実際の PostgreSQL コンテナをセットアップします。その後、pg ライブラリを使用してクライアントを初期化し、コンテナ内で稼働している PostgreSQL インスタンスに接続します。テストのセットアップの一環として、customer テーブルを作成します。

それでは、ソリューションを実装してみましょう。

async function createCustomerTable(client) { const sql = "CREATE TABLE IF NOT EXISTS customers (id INT NOT NULL, name VARCHAR NOT NULL, PRIMARY KEY (id))"; await client.query(sql); } async function createCustomer(client, customer) { const sql = "INSERT INTO customers (id, name) VALUES($1, $2)"; await client.query(sql, [customer.id, customer.name]); } async function getCustomers(client) { const sql = "SELECT * FROM customers"; const result = await client.query(sql); return result.rows; } module.exports = { createCustomerTable, createCustomer, getCustomers }

良さそうですが、うまく動作するでしょうか?

$ npm test > tc-guide-getting-started-with-testcontainers-for-nodejs@1.0.0 test PASS src/customer-repository.test.js Customer Repository should create and return multiple customers (5 ms) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 2.11 s, estimated 3 s Ran all test suites.

見てください! Testcontainers を使った最初のテストが成功しました。

実際の PostgreSQL インスタンスでテストする利点を実感できていますか?もしまだなら、customer テーブルの id 型を INT から BIGINT に変更してテストを再実行してみてください。どのような結果が予想されますか?もしユニットテストを行っていたり、Testcontainers の代わりにモックを使用していた場合、このバグを見逃していたかもしれません。

Testcontainers が内部でどのように動作しているかを確認したい場合もあるでしょう。本当にコンテナが起動しているのか?どのバージョンを使用しているのか?PostgreSQL は何をしているのか?これらをすべて確認するには、DEBUG 環境変数を設定します。次に、DEBUG=testcontainers* を指定してテストを再実行し、詳細を確認してみましょう。

$ DEBUG=testcontainers* npm test > tc-guide-getting-started-with-testcontainers-for-nodejs@1.0.0 test testcontainers [DEBUG] Loading ".testcontainers.properties" file... +0ms testcontainers [DEBUG] Loaded Docker client configuration, tcHost: "tcp://127.0.0.1:42317", dockerHost: "tcp://127.0.0.1:42317" +3ms testcontainers [DEBUG] Found Docker client strategy "UnixSocketStrategy" +0ms testcontainers [DEBUG] Testing Docker client strategy "unix:///var/run/docker.sock"... +0ms testcontainers [DEBUG] Fetching system info... +3ms testcontainers [DEBUG] Node version: v18.15.0, Platform: linux, Arch: x64, OS: Ubuntu 23.04, Version: 24.0.1, Arch: x86_64, CPUs: 32, Memory: 16746291200, Compose installed: true, Compose version: 1.29.2 +216ms testcontainers [INFO] Using Docker client strategy "UnixSocketStrategy", Docker host "localhost" (127.0.0.1) +1ms testcontainers [DEBUG] Not pulling image "postgres:13.3-alpine" as it already exists +2ms testcontainers [DEBUG] Creating new Reaper for session "2d8457b13f3d" with socket path "/var/run/docker.sock"... +0ms testcontainers [DEBUG] Not pulling image "testcontainers/ryuk:0.4.0" as it already exists +1ms testcontainers [INFO] Creating container for image "testcontainers/ryuk:0.4.0"... +0ms testcontainers [INFO] [a72827588430] Created container for image "testcontainers/ryuk:0.4.0" +52ms testcontainers [INFO] [a72827588430] Starting container for image "testcontainers/ryuk:0.4.0"... +0ms testcontainers [INFO] [a72827588430] Started container for image "testcontainers/ryuk:0.4.0" +249ms testcontainers [DEBUG] [a72827588430] Waiting for container to be ready... +2ms testcontainers [DEBUG] [a72827588430] Waiting for log message "/.+ Started!/"... +1ms testcontainers:containers [a72827588430] 2023/06/07 09:06:32 Pinging Docker... +0ms testcontainers:containers [a72827588430] 2023/06/07 09:06:32 Docker daemon is available! +0ms testcontainers:containers [a72827588430] 2023/06/07 09:06:32 Starting on port 8080... +0ms testcontainers:containers [a72827588430] 2023/06/07 09:06:32 Started! +0ms testcontainers [INFO] [a72827588430] Container is ready +2ms testcontainers [DEBUG] [a72827588430] Connecting to Reaper (attempt 1) on "localhost:32783"... +0ms testcontainers [DEBUG] [a72827588430] Connected to Reaper +1ms testcontainers [INFO] Creating container for image "postgres:13.3-alpine"... +0ms testcontainers:containers [a72827588430] 2023/06/07 09:06:32 New client connected: 172.17.0.1:46694 +3ms testcontainers:containers [a72827588430] 2023/06/07 09:06:32 Adding {"label":{"org.testcontainers.session-id=2d8457b13f3d":true}} +0ms testcontainers [INFO] [9d3296dd6c2a] Created container for image "postgres:13.3-alpine" +36ms testcontainers [INFO] [9d3296dd6c2a] Starting container for image "postgres:13.3-alpine"... +0ms testcontainers [INFO] [9d3296dd6c2a] Started container for image "postgres:13.3-alpine" +248ms testcontainers [DEBUG] [9d3296dd6c2a] Waiting for container to be ready... +1ms testcontainers [DEBUG] [9d3296dd6c2a] Waiting for log message "/.*database system is ready to accept connections.*/"... +0ms testcontainers:containers [9d3296dd6c2a] The files belonging to this database system will be owned by user "postgres". +285ms testcontainers:containers [9d3296dd6c2a] This user must also own the server process. +0ms testcontainers:containers [9d3296dd6c2a] +0ms testcontainers:containers [9d3296dd6c2a] The database cluster will be initialized with locale "en_US.utf8". +0ms testcontainers:containers [9d3296dd6c2a] The default database encoding has accordingly been set to "UTF8". +0ms testcontainers:containers [9d3296dd6c2a] The default text search configuration will be set to "english". +0ms testcontainers:containers [9d3296dd6c2a] +0ms testcontainers:containers [9d3296dd6c2a] Data page checksums are disabled. +0ms testcontainers:containers [9d3296dd6c2a] +0ms testcontainers:containers [9d3296dd6c2a] fixing permissions on existing directory /var/lib/postgresql/data ... ok +0ms testcontainers:containers [9d3296dd6c2a] creating subdirectories ... ok +0ms testcontainers:containers [9d3296dd6c2a] selecting dynamic shared memory implementation ... posix +0ms testcontainers:containers [9d3296dd6c2a] selecting default max_connections ... 100 +0ms testcontainers:containers [9d3296dd6c2a] selecting default shared_buffers ... 128MB +2ms testcontainers:containers [9d3296dd6c2a] selecting default time zone ... UTC +27ms testcontainers:containers [9d3296dd6c2a] creating configuration files ... ok +1ms testcontainers:containers [9d3296dd6c2a] running bootstrap script ... ok +47ms testcontainers:containers [9d3296dd6c2a] sh: locale: not found +92ms testcontainers:containers [9d3296dd6c2a] 2023-06-07 09:06:32.636 UTC [30] WARNING: no usable system locales were found +0ms testcontainers:containers [9d3296dd6c2a] performing post-bootstrap initialization ... ok +202ms testcontainers:containers [9d3296dd6c2a] syncing data to disk ... ok +46ms testcontainers:containers [9d3296dd6c2a] +0ms testcontainers:containers [9d3296dd6c2a] +0ms testcontainers:containers [9d3296dd6c2a] Success. You can now start the database server using: +0ms testcontainers:containers [9d3296dd6c2a] +0ms testcontainers:containers [9d3296dd6c2a] initdb: warning: enabling "trust" authentication for local connections +0ms testcontainers:containers [9d3296dd6c2a] You can change this by editing pg_hba.conf or using the option -A, or +0ms testcontainers:containers [9d3296dd6c2a] --auth-local and --auth-host, the next time you run initdb. +0ms testcontainers:containers [9d3296dd6c2a] pg_ctl -D /var/lib/postgresql/data -l logfile start +0ms testcontainers:containers [9d3296dd6c2a] +0ms testcontainers:containers [9d3296dd6c2a] waiting for server to start....2023-06-07 09:06:32.903 UTC [35] LOG: starting PostgreSQL 13.3 on x86_64-pc-linux-musl, compiled by gcc (Alpine 10.3.1_git20210424) 10.3.1 20210424, 64-bit +20ms testcontainers:containers [9d3296dd6c2a] 2023-06-07 09:06:32.910 UTC [35] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432" +7ms testcontainers:containers [9d3296dd6c2a] 2023-06-07 09:06:32.919 UTC [36] LOG: database system was shut down at 2023-06-07 09:06:32 UTC +9ms testcontainers:containers [9d3296dd6c2a] 2023-06-07 09:06:32.922 UTC [35] LOG: database system is ready to accept connections +2ms testcontainers:containers [9d3296dd6c2a] done +65ms testcontainers:containers [9d3296dd6c2a] server started +0ms testcontainers:containers [9d3296dd6c2a] CREATE DATABASE +81ms testcontainers:containers [9d3296dd6c2a] +0ms testcontainers:containers [9d3296dd6c2a] +0ms testcontainers:containers [9d3296dd6c2a] /usr/local/bin/docker-entrypoint.sh: ignoring /docker-entrypoint-initdb.d/* +0ms testcontainers:containers [9d3296dd6c2a] +0ms testcontainers:containers [9d3296dd6c2a] waiting for server to shut down....2023-06-07 09:06:33.068 UTC [35] LOG: received fast shutdown request +1ms testcontainers:containers [9d3296dd6c2a] 2023-06-07 09:06:33.071 UTC [35] LOG: aborting any active transactions +2ms testcontainers:containers [9d3296dd6c2a] 2023-06-07 09:06:33.071 UTC [35] LOG: background worker "logical replication launcher" (PID 42) exited with exit code 1 +0ms testcontainers:containers [9d3296dd6c2a] 2023-06-07 09:06:33.071 UTC [37] LOG: shutting down +0ms testcontainers:containers [9d3296dd6c2a] 2023-06-07 09:06:33.092 UTC [35] LOG: database system is shut down +22ms testcontainers:containers [9d3296dd6c2a] done +76ms testcontainers:containers [9d3296dd6c2a] server stopped +0ms testcontainers:containers [9d3296dd6c2a] +0ms testcontainers:containers [9d3296dd6c2a] PostgreSQL init process complete; ready for start up. +0ms testcontainers:containers [9d3296dd6c2a] +0ms testcontainers:containers [9d3296dd6c2a] 2023-06-07 09:06:33.180 UTC [1] LOG: starting PostgreSQL 13.3 on x86_64-pc-linux-musl, compiled by gcc (Alpine 10.3.1_git20210424) 10.3.1 20210424, 64-bit +12ms testcontainers:containers [9d3296dd6c2a] 2023-06-07 09:06:33.180 UTC [1] LOG: listening on IPv4 address "0.0.0.0", port 5432 +0ms testcontainers:containers [9d3296dd6c2a] 2023-06-07 09:06:33.180 UTC [1] LOG: listening on IPv6 address "::", port 5432 +0ms testcontainers:containers [9d3296dd6c2a] 2023-06-07 09:06:33.186 UTC [1] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432" +5ms testcontainers:containers [9d3296dd6c2a] 2023-06-07 09:06:33.193 UTC [49] LOG: database system was shut down at 2023-06-07 09:06:33 UTC +7ms testcontainers [INFO] [9d3296dd6c2a] Container is ready +731ms testcontainers:containers [9d3296dd6c2a] 2023-06-07 09:06:33.197 UTC [1] LOG: database system is ready to accept connections +7ms testcontainers [INFO] [9d3296dd6c2a] Stopping container... +27ms testcontainers:containers [9d3296dd6c2a] 2023-06-07 09:06:33.243 UTC [1] LOG: received fast shutdown request +43ms testcontainers:containers [9d3296dd6c2a] 2023-06-07 09:06:33.249 UTC [1] LOG: aborting any active transactions +6ms testcontainers:containers [9d3296dd6c2a] 2023-06-07 09:06:33.249 UTC [1] LOG: background worker "logical replication launcher" (PID 55) exited with exit code 1 +1ms testcontainers:containers [9d3296dd6c2a] 2023-06-07 09:06:33.249 UTC [50] LOG: shutting down +0ms testcontainers [INFO] [9d3296dd6c2a] Stopped container +351ms PASS src/customer-repository.test.js Customer Repository ✓ should create and return multiple customers (4 ms) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 2.138 s, estimated 3 s Ran all test suites

まとめ

Testcontainers を使用して、PostgreSQL データベースを利用したアプリケーションを Node.js でテストする方法を学びました。

Testcontainers を使った統合テストの記述方法は、IDE から実行可能なユニットテストの記述に非常によく似ていることがわかりました。また、チームメンバーがプロジェクトをクローンしても、ローカル環境に Postgres をインストールする必要がなく、同じテストを実行できる点が大きな利点です。

さらに、PostgreSQL だけでなく、Testcontainers は多くの一般的な SQL データベース、NoSQL データベース、メッセージキューなどに対応した専用モジュールも提供しています。これらのモジュールに加えて、Testcontainers を使用すれば、任意のコンテナ化された依存関係をテストに利用することも可能です。

Testcontainers についてさらに詳しく知りたい場合は、https://testcontainers.com をご覧ください。

参考資料

Last updated on