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
gotest gopher
go install github.com/mvrahden/go-test/cmd/gotest@latest
· CI Go Report Card
gotest spec ./pkg/user
$ 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.

user_service_suite_test.go
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)
        })
    })
}
You write Struct + methods with naming conventions
gotest generates The boilerplate you'd write by hand
go test runs Standard output, standard tooling. Your source tree stays clean.

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

CLI gotest ./... -v -race

Generate and test in one command

Linter gotest lint ./...

Catch common mistakes before they reach CI

VS Code mvrahden.gotest

Spec View, Test Explorer, coverage, debug

GitHub Action mvrahden/go-test@v1

PR annotations, step summaries, coverage reports