gotest
Specification-driven test suites for Go.
A go test code generator — you write structs with naming conventions, it handles the wiring.
go test under the hood
No runtime deps
No reflection
Pure code generation
go install github.com/mvrahden/go-test/cmd/gotest@latest
$ gotest spec ./pkg/user UserService Create when email is valid ✓ creates the user (8ms) ✓ sends a welcome email (120ms) when email already exists ✓ returns ErrDuplicate (<1ms) Delete ✓ soft-deletes the user (5ms) ~ hard-deletes after 30 days — SKIPPED 1 suite, 5 behaviors: 4 passed, 0 failed, 1 skipped
Under the hood it's go test. On the surface, your suites read as specifications.
Tests that verify. Specifications that document.
Convention-driven suites that read as documentation. Structured output for humans and machines.
✓ Behavioral Specifications
When groups context, It specifies behavior. Your tests are the documentation — always in sync, never stale.
✓ Lifecycle Hooks
Setup and teardown at the suite or test level. Define it once, every test gets it. No more copy-pasted boilerplate.
✓ Type-Safe Assertions
Generic assertions that catch type mismatches at compile time. Equality failures include diffs. Failures trace back to the test call site automatically — no t.Helper() needed.
✓ Snapshot Testing
Capture expected output once, catch regressions automatically. Snapshots are created on first run and verified on every run after.
✓ Fixtures
Share expensive setup like databases or containers across test suites, even across packages. Fixtures nest and compose naturally.
✓ Watch Mode
Save a file, see results instantly. Only affected packages re-run. Focus on a single test for a tight feedback loop.
Write structs. Get tests.
The naming conventions are the API. Your test structure is the specification.
type UserServiceTestSuite struct { svc *UserService } func (s *UserServiceTestSuite) BeforeEach(t *gotest.T) { s.svc = NewUserService() } func (s *UserServiceTestSuite) TestCreate(t *gotest.T) { t.When("email is valid", func(w *gotest.T) { w.It("creates the user", func(it *gotest.T) { err := s.svc.Create("alice@example.com") gotest.NoError(it, err) }) }) }
Coming from testify/suite?
Same test organization, different mechanism.
| testify/suite | gotest | |
|---|---|---|
| Runtime dependencies | Yes | None |
| Reflection | Yes | None |
| Registration boilerplate | suite.Run(t, new(...)) | None |
| Lock-in | High | Low — standard Go structs |
| BDD vocabulary | — | When / It |
| Snapshot testing | — | Built-in |
gotest migrate ./...
Automatic migration: renames hooks, rewrites assertions, removes testify imports.
Full ecosystem
gotest ./... -v -race
Generate and test in one command
gotest lint ./...
Catch common mistakes before they reach CI
mvrahden.gotest
Spec View, Test Explorer, coverage, debug
mvrahden/go-test@v1
PR annotations, step summaries, coverage reports