Testland
Browse all skills & agents

ginkgo-tests

Configures and runs Ginkgo - Go BDD test framework with `Describe` / `Context` / `It` nesting; `BeforeEach` / `AfterEach` / `JustBeforeEach` / `JustAfterEach` lifecycle; Gomega matchers DSL (`Expect(actual).To(Equal(expected))`); parallel execution via `-p`; focus (`F` prefix) + skip (`P` prefix); `DescribeTable` + `Entry` for parametrized tests; `ginkgo` CLI tool. Use when working with Go on a BDD-style test suite (Kubernetes-ecosystem standard).

Install with skills.sh (any agent)

npx skills add testland/qa --skill ginkgo-tests
View source

ginkgo-tests

Overview

Per onsi.github.io/ginkgo (opens in new window):

Ginkgo is a Go BDD framework. The Kubernetes ecosystem standardized on Ginkgo + Gomega for its test suites.

For non-BDD Go projects, go-test (stdlib) is the idiomatic choice. Ginkgo fits when:

  • The team has a BDD culture (rspec / mocha background).
  • Working on Kubernetes / CNCF projects (community convention).
  • Hierarchical test organization with Describe/Context/It reads better than flat func TestXxx.

Step 1 - Install

go install github.com/onsi/ginkgo/v2/ginkgo@latest
go get github.com/onsi/ginkgo/v2
go get github.com/onsi/gomega/...

Step 2 - Bootstrap

In the package to test:

ginkgo bootstrap         # creates <package>_suite_test.go
ginkgo generate calc     # creates calc_test.go template

<package>_suite_test.go:

package calc_test

import (
    "testing"

    . "github.com/onsi/ginkgo/v2"
    . "github.com/onsi/gomega"
)

func TestCalc(t *testing.T) {
    RegisterFailHandler(Fail)
    RunSpecs(t, "Calc Suite")
}

Step 3 - Spec structure

package calc_test

import (
    . "github.com/onsi/ginkgo/v2"
    . "github.com/onsi/gomega"
    "myproject/calc"
)

var _ = Describe("Calculator", func() {
    var c *calc.Calculator

    BeforeEach(func() {
        c = calc.New()
    })

    Describe("Add", func() {
        Context("with positive numbers", func() {
            It("adds correctly", func() {
                Expect(c.Add(1, 2)).To(Equal(3))
            })
        })

        Context("with overflow", func() {
            It("returns error", func() {
                _, err := c.AddSafe(math.MaxInt, 1)
                Expect(err).To(HaveOccurred())
            })
        })
    })
})

The var _ = Describe(...) pattern registers the spec at package init time. Ginkgo discovers + runs registered specs.

Step 4 - Lifecycle hooks

Per gn-docs (opens in new window):

BeforeSuite(func() { /* once before all specs */ })
AfterSuite(func() { /* once after all specs */ })
BeforeEach(func() { /* before each It in scope */ })
AfterEach(func() { /* after each It in scope */ })
JustBeforeEach(func() { /* after BeforeEach but before It */ })
JustAfterEach(func() { /* after It but before AfterEach */ })

Hooks nest with Describe/Context - BeforeEach in nested Context runs in addition to outer BeforeEach.

Step 5 - Gomega matchers

Per onsi.github.io/gomega (opens in new window):

Expect(value).To(Equal(expected))
Expect(err).To(HaveOccurred())
Expect(list).To(HaveLen(3))
Expect(value).To(BeNumerically(">", 0))
Eventually(func() bool { return ready() }).Should(BeTrue())

Eventually polls until the condition holds; Consistently polls to verify it stays true. Useful for async + concurrent code.

Full matcher DSL (strings, collections, numeric tolerance, panics, errors, channels): references/gomega.md.

Step 6 - DescribeTable + Entry

DescribeTable("Add",
    func(a, b, expected int) {
        Expect(calc.Add(a, b)).To(Equal(expected))
    },
    Entry("positive", 1, 2, 3),
    Entry("zero", 0, 0, 0),
    Entry("negative", -1, 1, 0),
    Entry("large", 100, 200, 300),
)

DescribeTable is Ginkgo's parametrize equivalent of pytest's @pytest.mark.parametrize or JUnit's @ParameterizedTest.

Step 7 - Focus + skip

FDescribe("focused", func() { ... })   // F prefix: only this runs
PDescribe("pending", func() { ... })   // P prefix: skipped

Same for FContext/PContext, FIt/PIt, FDescribeTable/PDescribeTable.

CI gating: lint forbid F prefixes via ginkgo --no-focus (errors if any focused specs in suite).

Step 8 - Parallel execution

ginkgo -p ./...                 # parallel; uses CPU count
ginkgo -procs=4 ./...           # explicit process count

Each parallel process runs a subset of specs. Tests must be independent - shared state breaks parallel runs.

Step 9 - CI integration

ginkgo -p --cover --coverprofile=coverage.out --no-focus -r

--no-focus fails the build if any F-prefix specs exist (catches debug-leftover focus).

Full GitHub Actions workflow (coverage upload, JUnit XML report): references/ci.md.

Anti-patterns

Anti-patternWhy it failsFix
Use Ginkgo for non-BDD codebaseVerbose vs stdlib testingUse go-test for non-BDD
Commit FDescribe / FIt accidentallySuite runs only focused specs--no-focus flag (Step 9)
Skip Eventually for async assertionsSleep-based polls are flakyUse Eventually/Consistently (Step 5)
Heavy nesting (5+ levels)Test setup hard to reason aboutFlatten with Describe+It
Use . import for both Ginkgo + Gomega without thinkingNamespace pollutionStandard convention; document

Limitations

  • BDD verbosity vs stdlib testing - pays off only when tests benefit from hierarchical grouping.
  • Adds two dependencies (Ginkgo + Gomega) to a Go project.
  • Kubernetes-ecosystem heavy convention; less common outside that community.
  • Parallel execution is per-process, not per-spec - startup cost amplifies for small suites.

References

Ginkgo CI integration

Per Ginkgo documentation (opens in new window).

GitHub Actions with coverage

- run: go install github.com/onsi/ginkgo/v2/ginkgo@latest
- run: ginkgo -p --cover --coverprofile=coverage.out --no-focus -r
- uses: codecov/codecov-action@v4
  with: { files: coverage.out }
  • -p runs specs in parallel across the CPU count.
  • --cover --coverprofile=coverage.out emits a Go coverage profile.
  • --no-focus fails the build if any F-prefix specs exist (catches debug-leftover focus).
  • -r recurses into all packages.

JUnit XML output

For CI systems that ingest JUnit test reports:

ginkgo --junit-report=junit.xml -r

This writes a JUnit-format report that most CI dashboards render as a per-spec pass/fail table.

Gating on focused specs

ginkgo --no-focus errors if the suite contains any focused (F-prefix) specs. Wire it into the CI run so an accidentally committed FDescribe or FIt fails the pipeline instead of silently skipping the rest of the suite.

Gomega matchers reference

View source (opens in new window)

Gomega matchers reference

Per Gomega documentation (opens in new window).

Expect(actual).To(matcher) and its negation Expect(actual).NotTo(matcher) are the core assertion forms.

Equality and nil

Expect(value).To(Equal(expected))
Expect(value).NotTo(Equal(unexpected))
Expect(value).To(BeNil())
Expect(value).To(BeTrue())

Strings

Expect(str).To(ContainSubstring("substring"))
Expect(str).To(MatchRegexp(`\d+`))

Collections

Expect(list).To(HaveLen(3))
Expect(list).To(ContainElement("alice"))
Expect(list).To(ConsistOf("alice", "bob"))   // unordered

Numeric

Expect(value).To(BeNumerically(">", 0))
Expect(value).To(BeNumerically("~", 3.14, 0.01))   // within tolerance

Panics, errors, channels

Expect(action).To(Panic())
Expect(err).To(MatchError("expected message"))
Expect(channel).To(Receive(&value))

Async polling

Eventually(func() bool { return ready() }).Should(BeTrue())
Consistently(func() bool { return stable() }).Should(BeTrue())

Eventually polls until the condition holds; Consistently polls to verify it stays true. Both suit async and concurrent code where a value settles over time rather than immediately.

Related skills

cargo-test

Configures and runs Rust's built-in `cargo test` - `#[test]` + `#[should_panic]` + `Result<(), E>` returns; integration tests in `tests/`; doc-tests embedded in `///` comments; `--lib` / `--bins` / `--all-targets` / `--workspace` selection; `cargo bench` (nightly) + Criterion (stable); `cargo test -- --test-threads=1` for serial; `cargo test -- --nocapture` to see println output. Use for any Rust project - testing is built into Cargo, no install needed.

go-rust-mocking

Isolates dependencies in Go and Rust unit tests using test-double generation - Go: `go.uber.org/mock` (gomock + mockgen codegen) and `github.com/stretchr/testify/mock` (hand-written stubs); Rust: `mockall` crate with `#[automock]` for traits and `mock!` macro for structs and external traits. Use when a unit test reaches a database, HTTP client, file system, or any interface boundary that must be replaced with a controlled fake to keep tests fast, deterministic, and isolated.

go-test

Configures and runs Go's stdlib `testing` package - `func TestXxx(t *testing.T)` convention; table-driven tests via slice + range; `t.Run` for subtests with hierarchical names; `t.Parallel()` for parallel execution; benchmarks (`func BenchmarkXxx(b *testing.B)`); examples (`func ExampleXxx()`); fuzzing (`func FuzzXxx(f *testing.F)`); coverage via `go test -cover`/`-coverprofile`; build tags for selective compilation. Use for any Go project - testing is a stdlib feature, no install needed.

rstest-tests

Configures and runs rstest - Rust parametrized + fixture-based testing crate; `#[rstest]` attribute + `#[case(...)]` for parametrize; `#[fixture]` for reusable test setup; matrix tests via multiple `#[case]` × N (cartesian product); async test support via `#[async_std::test]` / `#[tokio::test]` + `#[rstest]`; `#[future]` for async fixtures. Use when working with Rust and needing parametrize/fixture patterns beyond stdlib `#[test]`.