Skip to content

Test through the application

Let the application write, then inspect the result through the same database. Calling For(t) separately for the application and assertions would inspect different forks. These examples select one endpoint and pass it to both.

Build the binary from the repository root and export its absolute path. On Windows, choose an .exe output path and set the environment variable in your shell.

Terminal window
go build -o /tmp/pgmem-example ./cmd/pgmem
export PGMEM_BINARY=/tmp/pgmem-example

The fixture owns a subprocess and connects over TCP. The HTTP handler receives a sql.DB; assertions use a pgx pool from the same TestDB. Stop the HTTP server before closing the database clients and fork.

Terminal window
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)
}
}

Dependency manifests are included in each example directory. The Go replace, Node file: dependency and uv editable source point into this checkout. Replace them with published package dependencies when copying an example into another project. The code shown here is loaded from the files that CI executes.

A reset restores database state. Each fixture owns its HTTP server or engine, application memory, Valkey keys and OpenSearch indexes. See isolation choices and reset guarantees for a strategy that matches client lifetime and concurrency.