Ginkgo BDD Testing Framework for Go
repository·master·Indexed 27 days ago
https://github.com/onsi/ginkgoA 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.
What's inside Ginkgo
- 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.
Explore Ginkgo skills for Claude Code
masterOnce 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:overviewto 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 andjq.
Understand the Ginkgo parallelism mental model
masterGinkgo parallelism uses a separate-process model, not goroutines. When running with
-p:- Process Isolation: Ginkgo compiles the suite once and launches
Ncopies 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. - Suite Setup: Because they are separate processes, a standard
BeforeSuiteblock runs on every process. If you create a resource inBeforeSuite, it will be createdNtimes. - Coordination: The
ginkgoCLI acts as a server to coordinate the processes and merges their outputs into a single stream.
- Process Isolation: Ginkgo compiles the suite once and launches
Bootstrap and generate Ginkgo files
masterGinkgo provides generator commands to automate the creation of test files:
ginkgo bootstrap: Generates aPACKAGE_suite_test.gofile.ginkgo generate <SUBJECT>: Generates a<SUBJECT>_test.gofile (orPACKAGE_test.goif subject is omitted).
Custom Templates: Both commands support
--templatefor custom templates and--template-datato provide a JSON file containing custom data. Custom data is accessible in the template via the.CustomDataglobal key.Run Ginkgo suites with the CLI
masterThe Ginkgo CLI is the recommended tool for running suites, especially for parallel execution and profile aggregation. While
go testcan run suites, it does not support all Ginkgo features (e.g., useginkgo -repeat=Ninstead ofgo 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.
- Use
Use BeforeEach for common setup
masterTo 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
BeforeEachclosure. 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")) }) })Log output with `GinkgoWriter` and `By`
masterTo 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-vflag). This prevents passing tests from cluttering the console. UseGinkgoWriter.Printf(...)orGinkgoWriter.TeeTo(w)to stream live.By("..."): Use this to annotate specific steps within a longItblock. 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") // ... })Implement Shared Behaviors using closures
masterTo reuse a subset of spec behavior across different
Contextblocks, you can extract theItblocks into a shared-scope closure. This closure is called within the body of aContextcontainer block during the Tree Construction Phase.This allows the shared behavior to access variables defined in the outer scope (like a
bookvariable) while allowing eachContextto provide its own uniqueBeforeEachsetup.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 }) })Understand Ginkgo 2.0 Timeout and Interrupt Behavior
masterGinkgo 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
AfterEachblocks, skipping remaining tests, and then running theAfterSuiteblock. - Emits information about the interrupted node,
GinkgoWritercontents,stdout/stderrcontext, 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=1hguarantees the entire run exits after approximately one hour. - Default Timeout: The default timeout has been reduced from
24hto1h. Long-running tests in CI may require explicit adjustment via the-timeoutflag.
- Immediately causes the current test to unwind by running
Update ginkgo generate and bootstrap templates for --nodot
masterThe
ginkgo nodotsubcommand and--nodotflag (forbootstrapandgenerate) no longer support generating alias lists. If using--nodot, update your templates as follows:- Templates should no longer reference
{{.IncludeImports}}. - Use
{{.GinkgoImport}}and{{.GomegaImport}}for imports. - 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)) })- Templates should no longer reference
Resolve symbol clashes when upgrading to Ginkgo V2
masterIf 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() { ... })Handle goroutines safely in Ginkgo
masterWhen launching goroutines within a spec, follow these two critical rules to prevent suite crashes and hanging tests:
- Recover from panics: Any goroutine that might call
Failor a Gomega assertion must includedefer GinkgoRecover(). Without this, a failed assertion in a goroutine will cause a panic that crashes the entire test suite. - 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, useEventually(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 })- Recover from panics: Any goroutine that might call