Ginkgo BDD Testing Framework for Go

repository·master·Indexed 27 days ago

https://github.com/onsi/ginkgo

A mature, BDD-style testing framework for Go that provides an expressive DSL for writing unit, integration, and performance specs. Built on Go's standard testing package and often used with the Gomega matcher library, Ginkgo allows developers to organize tests into hierarchical structures using container nodes (Describe, Context, When) and subject nodes (It, Specify). It includes a CLI for suite management, parallel execution, and features for handling flaky specs and timeouts.

Tokens
64.3K
Snippets
144
Records
278
Agent score
94%

What's inside Ginkgo

  1. Overview of Ginkgo testing framework

    master
    Ginkgo is a testing framework for Go designed for writing expressive tests. While often described as a Behavior Driven Development (BDD) framework, it is a general-purpose tool used for unit tests, integration tests, acceptance tests, and performance tests. It is best used in conjunction with the Gomega matcher library to provide a rich Domain-specific Language (DSL) for testing. This documentation assumes you are using Ginkgo V2.
  2. Explore Ginkgo skills for Claude Code

    master

    Once installed, all Ginkgo skills are namespaced under ginkgo:. These skills activate automatically when working in a Go repository containing a Ginkgo suite.

    It is recommended to start with ginkgo:overview to understand the mental model (tree-construction-then-run) and spec independence before using other skills.

    Available skills include:

    • ginkgo:overview: Mental model and routing.
    • ginkgo:setup: Wiring Ginkgo into a repo (CLI, bootstrap, RegisterFailHandler, etc.).
    • ginkgo:writing-specs: Authoring specs (containers, subjects, DeferCleanup).
    • ginkgo:tables-and-dynamic-specs: Data-driven specs (DescribeTable, Entry).
    • ginkgo:decorators: Reference for decorators.
    • ginkgo:filtering: Running subsets (focus, skip, labels, filters).
    • ginkgo:parallelism: Parallel execution (-p, SynchronizedBeforeSuite, sharding).
    • ginkgo:ordering-and-flakes: Managing order (Ordered/Serial) and flakiness.
    • ginkgo:timeouts-and-async: Async behavior and timeouts (Eventually, goroutines).
    • ginkgo:running: Running suites (CLI, randomization, watching).
    • ginkgo:ci: CI setup and hardening.
    • ginkgo:reporting: Report files and programmatic reporters.
    • ginkgo:debugging-failures: Diagnosing failures using JSON and jq.
  3. Understand the Ginkgo parallelism mental model

    master

    Ginkgo parallelism uses a separate-process model, not goroutines. When running with -p:

    1. Process Isolation: Ginkgo compiles the suite once and launches N copies of the test binary as separate OS processes. Each process has its own memory space. Package-level variables and closures are not shared across processes.
    2. Suite Setup: Because they are separate processes, a standard BeforeSuite block runs on every process. If you create a resource in BeforeSuite, it will be created N times.
    3. Coordination: The ginkgo CLI acts as a server to coordinate the processes and merges their outputs into a single stream.
  4. Bootstrap and generate Ginkgo files

    master

    Ginkgo provides generator commands to automate the creation of test files:

    • ginkgo bootstrap: Generates a PACKAGE_suite_test.go file.
    • ginkgo generate <SUBJECT>: Generates a <SUBJECT>_test.go file (or PACKAGE_test.go if subject is omitted).

    Custom Templates: Both commands support --template for custom templates and --template-data to provide a JSON file containing custom data. Custom data is accessible in the template via the .CustomData global key.

  5. Run Ginkgo suites with the CLI

    master

    The Ginkgo CLI is the recommended tool for running suites, especially for parallel execution and profile aggregation. While go test can run suites, it does not support all Ginkgo features (e.g., use ginkgo -repeat=N instead of go test -count=N).

    Command Syntax: ginkgo <GINKGO-FLAGS> <PACKAGES> -- <PASS-THROUGHS>

    • Use -- to pass arguments directly to the suite rather than to the Ginkgo CLI.
    • Ginkgo flags must appear before the list of packages.
  6. Use BeforeEach for common setup

    master

    To remove duplication and share setup across multiple specs, use BeforeEach(<closure>) setup nodes.

    Best Practice: To ensure specs remain independent (allowing for parallel execution and shuffling), follow the pattern: "Declare in container nodes, initialize in setup nodes". Declare shared variables within the container node closure and initialize them inside the BeforeEach closure. This ensures each spec receives a pristine, correctly initialized copy of the variable.

    var _ = Describe("Books", func() {
      var book *books.Book
    
      BeforeEach(func() {
        book = &books.Book{
          Title: "Les Miserables",
          Author: "Victor Hugo",
          Pages: 2783,
        }
        Expect(book.IsValid()).To(BeTrue())
      })
    
      It("can extract the author's last name", func() {
        Expect(book.AuthorLastName()).To(Equal("Hugo"))
      })
    })
  7. Log output with `GinkgoWriter` and `By`

    master

    To keep test output clean while still providing debugging information:

    • GinkgoWriter: Use this to buffer logs. Logs are only printed when a spec fails (or when running with the -v flag). This prevents passing tests from cluttering the console. Use GinkgoWriter.Printf(...) or GinkgoWriter.TeeTo(w) to stream live.
    • By("..."): Use this to annotate specific steps within a long It block. These annotations are recorded in the spec's timeline and surface during failures (or under -v) to show exactly how far the spec progressed before failing.
    It("processes an order", func() {
    	By("submitting the cart")
    	// ...
    	By("charging the card")
    	// ...
    })
  8. Implement Shared Behaviors using closures

    master

    To reuse a subset of spec behavior across different Context blocks, you can extract the It blocks into a shared-scope closure. This closure is called within the body of a Context container block during the Tree Construction Phase.

    This allows the shared behavior to access variables defined in the outer scope (like a book variable) while allowing each Context to provide its own unique BeforeEach setup.

    Describe("Storing books in the library", func() {
      var book *books.Book{}
    
      // Define the shared behavior as a closure
      AssertFailedBehavior := func() {
        It("validates that the book can't be stored", func() {
          Expect(library.IsStorable(book)).To(BeFalse())
        })
    
        It("fails to store the book", func() {
          Expect(library.Store(book)).To(MatchError(books.ErrStoringBook))
        })
      }
    
      Context("when the book has no title", func() {
        BeforeEach(func() {
          book = &books.Book{
            Author:  "Victor Hugo",
            Pages:   2783,
          }
        })
    
        AssertFailedBehavior() // Execute the shared behavior
      })
    
      Context("when the book is nil", func() {
        BeforeEach(func() {
          book = nil
        })
    
        AssertFailedBehavior() // Execute the shared behavior
      })
    })
  9. Understand Ginkgo 2.0 Timeout and Interrupt Behavior

    master

    Ginkgo 2.0 introduces improved handling for interrupts and timeouts to ensure graceful test suite unwinding.

    Interrupt Behavior

    Sending an interrupt signal (e.g., Ctrl+C) now:

    • Immediately causes the current test to unwind by running AfterEach blocks, skipping remaining tests, and then running the AfterSuite block.
    • Emits information about the interrupted node, GinkgoWriter contents, stdout/stderr context, and a stack trace of every running goroutine.
    • Does not exit immediately on a second interrupt; Ginkgo will wait until the suite has fully unwound.

    Timeout Behavior

    • Graceful Unwinding: Ginkgo now manages its own timeouts. When a timeout triggers, the suite winds down gracefully (running AfterEach/AfterSuite), making it functionally equivalent to a user-initiated interrupt.
    • Suite-wide Timeout: In V2, the timeout applies to the entire test suite run. For example, ginkgo -r -timeout=1h guarantees the entire run exits after approximately one hour.
    • Default Timeout: The default timeout has been reduced from 24h to 1h. Long-running tests in CI may require explicit adjustment via the -timeout flag.
  10. Update ginkgo generate and bootstrap templates for --nodot

    master

    The ginkgo nodot subcommand and --nodot flag (for bootstrap and generate) no longer support generating alias lists. If using --nodot, update your templates as follows:

    1. Templates should no longer reference {{.IncludeImports}}.
    2. Use {{.GinkgoImport}} and {{.GomegaImport}} for imports.
    3. Use {{.GinkgoPackage}} and {{.GomegaImport}} to reference exported names.

    Example Template Snippet:

    import (
    	{{.GinkgoImport}}
    	{{.GomegaImport}}
    )
    
    var _ = {{.GinkgoPackage}}It("is templated", func() {
    	{{.GomegaPackage}}Expect(foo).To({{.GomegaPackage}}Equal(bar))
    })
  11. Resolve symbol clashes when upgrading to Ginkgo V2

    master

    If upgrading to Ginkgo V2 causes symbol clashes in your codebase (common when using dot-imports), you can avoid the clash by using the namespaced DSL packages instead of a single dot-import of the entire Ginkgo package.

    Specifically, you can dot-import the core DSL and import decorators or other components separately.

    import (
    	. "github.com/onsi/ginkgo/v2/dsl/core" 	
    	"github.com/onsi/ginkgo/v2/dsl/decorators" 	
    )
    
    var _ = It("gives you the core DSL", decorators.Label("and namespaced decorators"), func() {
    	...
    })
  12. Handle goroutines safely in Ginkgo

    master

    When launching goroutines within a spec, follow these two critical rules to prevent suite crashes and hanging tests:

    1. Recover from panics: Any goroutine that might call Fail or a Gomega assertion must include defer GinkgoRecover(). Without this, a failed assertion in a goroutine will cause a panic that crashes the entire test suite.
    2. Poll channels, don't block on them: Do not use a bare channel receive (e.g., <-done) to wait for a goroutine. If the goroutine fails before closing the channel, the spec will hang until it times out. Instead, use Eventually(done).Should(BeClosed()) so that failures surface immediately.

    Correct Pattern:

    It("repaginates", func() {
      done := make(chan any)
      go func() {
        defer GinkgoRecover()                 // RULE 1: Protect the suite
        Expect(book.SetFontSize(28)).To(Succeed())
        close(done)
      }()
      Eventually(done).Should(BeClosed())     // RULE 2: Poll, don't block
    })
    It("repaginates", func() {
      done := make(chan any)
      go func() {
        defer GinkgoRecover()                 // RULE 1
        Expect(book.SetFontSize(28)).To(Succeed())
        close(done)
      }()
      Eventually(done).Should(BeClosed())     // RULE 2: poll, don't block on <-done
    })