gotest

Specification-driven test suites for AI-assisted Go development.

No runtime deps No reflection No lock-in
gotest gopher
go install github.com/mvrahden/go-test/cmd/gotest@latest
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
2 suites, 5 behaviors: 4 passed, 0 failed, 1 skipped

Your test suites are behavioral specifications. Every suite tells a story.

Tests that verify. Specifications that document.

Convention-driven suites that read as documentation. Structured output for AI-assisted workflows.

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. Works with both suite and standalone tests.

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. Generated files are deleted after.

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 None — eject freely
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, test, cleanup in one command

Linter gotest lint ./...

Catch common mistakes before they reach CI

VS Code mvrahden.gotest

Spec View, Test Explorer, coverage, debug

CI gotest --ci ./...

Coverage thresholds, safety checks, spec reports