Go's built-in go test is powerful but its output is raw. gest brings Jest's developer experience to Go: a fluent assertion API (Describe / It / Expect), colorized output, descriptive failure messages — all running on top of the native go test engine so you get caching, -race, real coverage and full IDE support for free.
gest has two parts that work together:
| Part | What it does |
|---|---|
lib github.com/caiolandgraf/gest/v2/gest |
Provides Describe, It, Expect and all matchers. You write tests with it inside standard _test.go files using Suite.Run(t). |
CLI github.com/caiolandgraf/gest/v2/cmd/gest |
Runs go test -v -json under the hood, parses the event stream, and renders the beautiful Jest-style output. |
# Add the library to your project
go get github.com/caiolandgraf/gest/v2
# Install the CLI globally
go install github.com/caiolandgraf/gest/v2/cmd/gest@latestmy-project/
├── go.mod
├── calculator.go
├── calculator_test.go ← standard Go test file using gest's API
├── user.go
└── user_test.go
Standard
_test.gofiles. gest now works with Go's native test convention. Thego testtoolchain discovers and runs them — gest just makes writing and reading them a pleasure.
calculator_test.go — write tests with the Jest-style API, run them with Suite.Run(t):
package mypackage
import (
"testing"
"github.com/caiolandgraf/gest/v2/gest"
)
func TestCalculator(t *testing.T) {
calc := Calculator{}
s := gest.Describe("calculator")
s.It("adding 2 + 2 should return 4", func(t *gest.T) {
t.Expect(calc.Add(2, 2)).ToBe(float64(4))
})
s.It("dividing by zero should return an error", func(t *gest.T) {
_, err := calc.Divide(10, 0)
t.Expect(err).Not().ToBeNil()
})
s.Run(t)
}Run with the gest CLI to get the beautiful output:
gest ./...Or use plain go test — everything still works, just without the colors:
go test ./...Pass --watch (or -w) to enter watch mode. gest re-runs go test automatically whenever any .go file changes — no recompilation overhead, just the native go test cache at full speed.
gest --watch ./... # watch mode
gest -w ./... # shorthand
gest --watch -c ./... # watch + coverage table- Rapid saves are collapsed via a 30 ms debounce — one clean result per save.
- The terminal is cleared before each re-run so the output is always fresh.
- Press
Ctrl+Cto stop.
When a test fails, gest shows exactly what went wrong:
Pass -c or --coverage to display the per-suite pass rate table:
gest -c
gest --coverageBar colors: 🟢 green ≥ 80% · 🟡 yellow ≥ 50% · 🔴 red < 50%
| Matcher | Description |
|---|---|
.ToBe(v) |
Strict equality (==) |
.ToEqual(v) |
Deep equality (reflect.DeepEqual) |
.ToBeNil() |
Value is nil |
.ToBeTrue() |
Value is true |
.ToBeFalse() |
Value is false |
.ToContain(s) |
String contains substring |
.ToHaveLength(n) |
Length of string, slice or map |
.ToBeGreaterThan(n) |
Number greater than n |
.ToBeLessThan(n) |
Number less than n |
.ToBeCloseTo(n, delta?) |
Float approximately equal (default ±0.001) |
Any matcher can be negated with .Not():
t.Expect(err).Not().ToBeNil()
t.Expect("gest").Not().ToContain("jest")
t.Expect(result).Not().ToBe(float64(0))// Create a suite
s := gest.Describe("suite name")
// Add test cases — It() returns *Suite for chaining
s.It("description", func(t *gest.T) {
t.Expect(value).ToBe(expected)
})
// Run all Its as subtests under a standard *testing.T
func TestMyFeature(t *testing.T) {
s := gest.Describe("my feature")
s.It("does something", func(t *gest.T) { ... })
s.Run(t) // hands off to go test
}
// Fluent chaining
gest.Describe("calculator").
It("adds", func(t *gest.T) { ... }).
It("subtracts", func(t *gest.T) { ... }).
Run(t)See the examples/ folder for a working project.
cd examples
# Run with the beautiful gest output
go run ../cmd/gest ./...
# Or plain go test
go test ./...
# With coverage table
go run ../cmd/gest -c ./...
# Watch mode
go run ../cmd/gest --watch ./...v1 used *_spec.go files with init() / Register() / RunRegistered() and a main.go entry point. v2 drops all of that in favour of standard Go test files.
| v1 | v2 |
|---|---|
*_spec.go files with package main |
*_test.go files with your package name |
func init() { gest.Register(s) } |
No registration needed |
func main() { gest.RunRegistered() } |
No main.go needed |
go run . to execute tests |
go test ./... or gest ./... |
| Watch mode built into the lib | Watch mode in the CLI (gest --watch ./...) |
1. Rename your spec files
# rename *_spec.go → *_test.go
mv calculator_spec.go calculator_test.go2. Change the package and remove boilerplate
// before (v1)
package main
import "github.com/caiolandgraf/gest/v2/gest"
var s = gest.Describe("calculator")
func init() {
s.It("adds", func(t *gest.T) { ... })
gest.Register(s)
}// after (v2)
package mypackage // ← your actual package name
import (
"testing"
"github.com/caiolandgraf/gest/v2/gest"
)
func TestCalculator(t *testing.T) {
s := gest.Describe("calculator")
s.It("adds", func(t *gest.T) { ... })
s.Run(t) // ← hands off to go test
}3. Delete main.go
The main.go that called gest.RunRegistered() is no longer needed. Delete it.
4. Install the CLI
go install github.com/caiolandgraf/gest/v2/cmd/gest@latest5. Run
gest ./... # beautiful output (replaces go run .)
go test ./... # plain go test also works- Powered by
go test— caching,-racedetector, real line coverage, IDE support, all for free - Familiar API — if you know Jest, you already know gest
- Beautiful output — colors, progress bars, failure diffs
- Zero friction — standard
_test.gofiles, no config, no separate test binary - Minimal dependencies — only fsnotify for watch mode
MIT
| Name | |
|---|---|
| @caiolandgraf |
Made with 🧪 by @caiolandgraf · caiolandgraf.github.io/gest



