コンテンツにスキップ

アプリ経由で書き込みを検証する

まずアプリが書き込み、テストが同じ DB で結果を確かめます。アプリと検証用に別々の For(t) を呼ぶと、異なるフォークを検証してしまいます。以下のサンプルは接続先を一度選び、両方へ渡します。

リポジトリのルートでバイナリをビルドし、絶対パスを設定してください。Windows では .exe の出力先を選び、利用する shell で環境変数を設定します。

ターミナルウィンドウ
go build -o /tmp/pgmem-example ./cmd/pgmem
export PGMEM_BINARY=/tmp/pgmem-example

外部プロセスの TCP 接続を使います。HTTP ハンドラに渡す sql.DB と、検証用の pgx pool は一つの TestDB から取得します。HTTP サーバーを止めてから DB の接続とフォークを閉じます。

ターミナルウィンドウ
go -C examples/go-http test -v
examples/go-http/app.go
package orders
import (
"database/sql"
"encoding/json"
"net/http"
)
// NewHandler receives its database before it creates any clients.
func NewHandler(db *sql.DB) http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("POST /orders", func(w http.ResponseWriter, r *http.Request) {
var order struct {
ID int `json:"id"`
}
if err := json.NewDecoder(r.Body).Decode(&order); err != nil || order.ID <= 0 {
http.Error(w, "invalid order", http.StatusBadRequest)
return
}
if _, err := db.ExecContext(r.Context(), "INSERT INTO orders(id) VALUES ($1)", order.ID); err != nil {
http.Error(w, "cannot create order", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusCreated)
})
return mux
}
examples/go-http/app_test.go
package orders
import (
"context"
"database/sql"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
"github.com/shibukawa/pgmem/pgmemprocess"
)
var fixture *pgmemprocess.Fixture
func TestMain(m *testing.M) {
os.Exit(pgmemprocess.Run(m, pgmemprocess.FixtureOptions{
Options: pgmemprocess.Options{Transport: "tcp", Database: "orders"},
Prepare: func(ctx context.Context, db *sql.DB, _ string) error {
_, err := db.ExecContext(ctx, "CREATE TABLE orders(id int PRIMARY KEY)")
return err
},
}, func(f *pgmemprocess.Fixture) { fixture = f }))
}
func TestCreateOrder(t *testing.T) {
t.Parallel()
database := fixture.For(t)
server := httptest.NewServer(NewHandler(database.DB()))
t.Cleanup(server.Close) // HTTP server closes before the database handles.
response, err := http.Post(server.URL+"/orders", "application/json", strings.NewReader(`{"id":42}`))
if err != nil {
t.Fatal(err)
}
response.Body.Close()
if response.StatusCode != http.StatusCreated {
t.Fatalf("status: %d", response.StatusCode)
}
var id int
if err := database.PgxPool().QueryRow(t.Context(), "SELECT id FROM orders").Scan(&id); err != nil || id != 42 {
t.Fatalf("application write missing from same fork: %d %v", id, err)
}
}
func TestAnotherTestStartsEmpty(t *testing.T) {
t.Parallel()
var count int
if err := fixture.For(t).DB().QueryRow("SELECT count(*) FROM orders").Scan(&count); err != nil || count != 0 {
t.Fatalf("another test leaked data: %d %v", count, err)
}
}

各ディレクトリには依存関係の設定も含まれています。Go の replace、Node の file:、uv の editable source はこの checkout を参照します。別のプロジェクトへ移すときは公開パッケージの依存に置き換えてください。表示しているコードは実際に CI で実行するファイルから読み込んでいます。

reset が戻すのは DB の状態です。HTTP サーバーや engine、アプリのメモリ、Valkey のキー、OpenSearch の index は各 fixture が管理します。分離方式と reset の保証範囲で、クライアントの寿命と並列実行に合う方式を選べます。