Problem description
I have a Go project with integration tests that use a MySQL test database. Each package has its own tests (e.g. cmd/web, internal/models), and each package sets up and tears down the database schema using setup.sql and teardown.sql.
Individually, running tests per package works fine:
go test ./internal/models→ passesgo test ./cmd/web→ passes
However, when running the full suite: go test ./…
I started getting intermittent failures such as:
Error 1050: Table 'snippets' already exists- teardown not consistently removing schema state
- tests passing or failing depending on which packages were included
Initially, I assumed this was a cleanup issue in my setup/teardown logic. But after investigation, I realized the key issue:
- Each package is compiled into a separate test binary
go test ./...runs multiple test binaries concurrently- All test packages were connecting to the same MySQL database (
test_snippetbox)
This caused race conditions where:
- one package’s setup interfered with another package’s teardown
- schema creation happened while another package still assumed a clean DB
Running with -p=1 confirmed the issue was concurrency-related.
Solution I ended up using
I changed the test setup so that each test package gets its own isolated database, e.g.:
test_snippetbox_cmd_webtest_snippetbox_internal_models
Each package:
- creates its own database in setup
- runs migrations / setup.sql inside that database
- drops the database in teardown
This removed all cross-package interference and made go test ./... fully stable even with parallel execution.
Question
Is this the recommended approach in Go for integration tests with external databases (i.e. per-package database isolation), or is there a more idiomatic pattern that is generally preferred in production Go projects?