Skip to content

Latest commit

 

History

History
378 lines (289 loc) · 8.33 KB

File metadata and controls

378 lines (289 loc) · 8.33 KB

gest 🧪

A Jest-like testing framework for Go with powerful hooks, nested describes, and an interactive watch mode.

Features

Expressive BDD-style syntax - Familiar Describe, It, BeforeAll, BeforeEach, AfterEach, and AfterAll hooks

🔗 Hook Inheritance - Hooks from parent Describe blocks are automatically inherited by nested blocks

🎯 Rich Expectations - Fluent API with Expect() and matchers like ToBe(), ToEqual(), ToContain(), and more

👀 Interactive Watch Mode - File watching with smart filtering and interactive controls

🎨 Beautiful Output - Color-coded test results with clear failure messages and code snippets

Installation

go get github.com/caiolandgraf/gest/v2

Install the CLI tool:

go install github.com/caiolandgraf/gest/v2/cmd/gest@latest

Quick Start

Create a test file:

package mypackage

import (
    "testing"
    "github.com/caiolandgraf/gest/v2/gest"
)

func TestCalculator(t *testing.T) {
    gest.Describe("Calculator").
        It("adds numbers", func(t *gest.T) {
            t.Expect(2 + 2).ToBe(4)
        }).
        It("subtracts numbers", func(t *gest.T) {
            t.Expect(5 - 3).ToBe(2)
        }).
        Run(t)
}

Run your tests:

gest ./...

Hooks

BeforeAll and AfterAll

Run once before or after all tests in a suite:

func TestDatabase(t *testing.T) {
    var db *Database

    gest.Describe("Database").
        BeforeAll(func(t *gest.T) {
            db = connectToDatabase()
            t.Expect(db).Not().ToBeNil()
        }).
        AfterAll(func(t *gest.T) {
            db.Close()
        }).
        It("queries data", func(t *gest.T) {
            result := db.Query("SELECT * FROM users")
            t.Expect(result).Not().ToBeNil()
        }).
        Run(t)
}

BeforeEach and AfterEach

Run before or after each test in a suite:

func TestUserService(t *testing.T) {
    var user *User

    gest.Describe("UserService").
        BeforeEach(func(t *gest.T) {
            user = &User{Name: "Test User"}
        }).
        AfterEach(func(t *gest.T) {
            user = nil
        }).
        It("creates a user", func(t *gest.T) {
            t.Expect(user.Name).ToBe("Test User")
        }).
        It("updates a user", func(t *gest.T) {
            user.Name = "Updated"
            t.Expect(user.Name).ToBe("Updated")
        }).
        Run(t)
}

Hook Inheritance

Hooks from parent Describe blocks are automatically inherited by nested blocks:

func TestNestedHooks(t *testing.T) {
    var outerSetup int
    var innerSetup int

    gest.Describe("Outer Suite").
        BeforeEach(func(t *gest.T) {
            outerSetup++  // Runs for ALL tests
        }).
        It("outer test", func(t *gest.T) {
            t.Expect(outerSetup).ToBe(1)
        }).
        Describe("Inner Suite", func(s *gest.Suite) {
            s.BeforeEach(func(t *gest.T) {
                innerSetup++  // Runs only for inner tests
            })

            s.It("inner test", func(t *gest.T) {
                // Parent BeforeEach ran first
                t.Expect(outerSetup).ToBe(2)
                // Then child BeforeEach ran
                t.Expect(innerSetup).ToBe(1)
            })
        }).
        Run(t)
}

Hook execution order:

  1. Parent BeforeAll → Child BeforeAll
  2. Parent BeforeEach → Child BeforeEach → Test → Child AfterEach → Parent AfterEach
  3. Child AfterAll → Parent AfterAll

Nested Describes

Organize tests hierarchically with nested Describe blocks:

func TestCalculator(t *testing.T) {
    gest.Describe("Calculator").
        Describe("Addition", func(s *gest.Suite) {
            s.It("adds positive numbers", func(t *gest.T) {
                t.Expect(2 + 3).ToBe(5)
            })
            
            s.It("adds negative numbers", func(t *gest.T) {
                t.Expect(-2 + -3).ToBe(-5)
            })
        }).
        Describe("Multiplication", func(s *gest.Suite) {
            s.It("multiplies positive numbers", func(t *gest.T) {
                t.Expect(2 * 3).ToBe(6)
            })
        }).
        Run(t)
}

Expectations

Basic Matchers

t.Expect(value).ToBe(expected)           // Same reference (==)
t.Expect(value).ToEqual(expected)        // Deep equality
t.Expect(value).ToBeNil()                // Check for nil
t.Expect(value).ToBeTrue()               // Boolean true
t.Expect(value).ToBeFalse()              // Boolean false

Negation

t.Expect(value).Not().ToBe(unexpected)
t.Expect(value).Not().ToBeNil()

Numeric Comparisons

t.Expect(value).ToBeGreaterThan(5)
t.Expect(value).ToBeGreaterThanOrEqual(5)
t.Expect(value).ToBeLessThan(10)
t.Expect(value).ToBeLessThanOrEqual(10)
t.Expect(value).ToBeCloseTo(3.14, 0.01)  // Within delta
t.Expect(value).ToBeNaN()

Collections

t.Expect(slice).ToContain(item)
t.Expect(slice).ToHaveLength(5)

Type Checking

t.Expect(value).ToBeInstanceOf(reflect.TypeOf(&User{}))

Functions

t.Expect(func() { panic("error") }).ToThrow()

Object Matching

t.Expect(user).ToMatchObject(map[string]interface{}{
    "Name": "John",
    "Age":  30,
})

Watch Mode

Start watch mode to automatically re-run tests when files change:

gest --watch ./...

Interactive Controls

When in watch mode, press:

  • f - Run only failed tests
  • o - Run only tests related to changed files
  • p - Filter by filename regex pattern
  • t - Filter by test name regex pattern
  • c - Clear all filters
  • Enter - Trigger a test run
  • q - Quit watch mode

Examples

Filter by filename:

Press p
> Filename regex pattern: calculator

Filter by test name:

Press t
> Test name regex pattern: Addition

CLI Options

gest [options] [packages...]

Options:
  --watch, -w         Enable watch mode
  --coverage, -c      Show coverage information
  --no-cache          Disable test caching
  --debounce=80ms     Debounce duration for file changes
  -p pattern          Filter by filename regex
  
  # Pass additional flags to go test
  --                  Everything after this is passed to go test

Examples:

# Run all tests
gest ./...

# Watch mode with coverage
gest --watch --coverage ./...

# Filter by filename pattern
gest -p "calculator" ./...

# Pass flags to go test
gest -- -race -timeout 30s ./...

Project Structure

gest/
├── gest/
│   └── gest.go          # Core testing library
├── cmd/
│   └── gest/
│       └── main.go      # CLI tool with watch mode
└── example/
    ├── calculator_test.go
    └── math_test.go

How It Works

gest is built on top of Go's standard testing package. Each Describe suite is converted to a t.Run() subtest, which means:

  • ✅ Works with all Go testing tools (go test, IDEs, CI/CD)
  • ✅ Parallel test execution support (via t.Parallel())
  • ✅ Test filtering with -run flag
  • ✅ Coverage reporting
  • ✅ Benchmarking support

Advanced Features

Method Chaining

All methods return *Suite for fluent chaining:

gest.Describe("Suite").
    BeforeAll(setup).
    BeforeEach(prepare).
    It("test 1", test1).
    It("test 2", test2).
    AfterEach(cleanup).
    AfterAll(teardown).
    Run(t)

Multiple Nested Levels

gest.Describe("Level 1").
    Describe("Level 2", func(s *gest.Suite) {
        s.Describe("Level 3", func(s *gest.Suite) {
            s.It("deeply nested test", func(t *gest.T) {
                // All parent hooks run before this
            })
        })
    }).
    Run(t)

Comparison with Jest

Feature Jest (JavaScript) gest (Go)
describe() Describe()
it() / test() It()
beforeAll() BeforeAll()
afterAll() AfterAll()
beforeEach() BeforeEach()
afterEach() AfterEach()
Nested describes
Hook inheritance
Watch mode
Matchers
Native integration N/A ✅ Go testing

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT

Credits

Inspired by Jest - the delightful JavaScript testing framework.