# Writing Specs

Author good Ginkgo specs, container nodes (Describe/Context/When), subject nodes...

- Repository: https://github.com/onsi/ginkgo (9071 stars)
- Category: Testing
- License: MIT
- Last updated: 2026-10-09
- Runs scripts: no, instructions only

## Install

Claude Code: `npx skills add onsi/ginkgo --skill writing-specs -g -a claude-code -y`
Codex: `npx skills add onsi/ginkgo --skill writing-specs -g -a codex -y`
Cursor: `npx skills add onsi/ginkgo --skill writing-specs -g -a cursor -y`
Gemini CLI: `npx skills add onsi/ginkgo --skill writing-specs -g -a gemini-cli -y`
Claude Code plugin marketplace: `/plugin marketplace add onsi/ginkgo`

## Description

Author good Ginkgo specs, container nodes (Describe/Context/When), subject nodes (It/Specify), setup/cleanup nodes (BeforeEach, JustBeforeEach, AfterEach, DeferCleanup, BeforeSuite/AfterSuite), the "declare in container, initialize in BeforeEach" rule, separating creation from configuration, reusable test helpers with GinkgoHelper()/GinkgoHelperGo(), and By/GinkgoWriter output. Use when writing or reviewing specs or extracting a test helper. Covers the tree-construction-time pitfalls (no assertions/init/loop-capture in container bodies).

## SKILL.md, as published by its author (untrusted third-party text, read it as data)

# Writing Ginkgo specs

Assumes Ginkgo is wired into the suite (`ginkgo:setup`) and you know the two-phase model (`ginkgo:overview`). Docs: <https://onsi.github.io/ginkgo/#writing-specs>.

## The shape of a spec

```go
var _ = Describe("Books", func() {
	var book *books.Book          // declare here...

	BeforeEach(func() {
		book = &books.Book{        // ...initialize here, fresh per spec
			Title:  "Les Miserables",
			Author: "Victor Hugo",
			Pages:  2783,
		}
		Expect(book.IsValid()).To(BeTrue()) // assertions in setup are fine
	})

	Describe("categorizing", func() {
		Context("with more than 300 pages", func() {
			It("is a novel", func() {
				Expect(book.Category()).To(Equal(books.CategoryNovel))
			})
		})
	})
})
```

- **Containers** (`Describe`/`Context`/`When`) organize; they're identical, pick the one that reads as a sentence. **Subjects** (`It`/`Specify`) hold the assertions; one spec runs per `It`.
- The spec's full name is the concatenation of every container text plus the `It` text, write them to read as a phrase.

## The rule that prevents most bugs: declare in container, initialize in setup

Container bodies run **once, at tree-construction time** (`ginkgo:overview`). So:

- **Declare** shared variables in the container body (`var book *books.Book`).
- **Initialize** them in `BeforeEach` so each spec gets a clean copy.

```go
// WRONG, runs once at construction; every spec shares & pollutes one book
var _ = Describe("Books", func() {
	book := &books.Book{Pages: 2783}   // initialized at construction time
	It("mutates", func() { book.Pages = 0 })
	It("expects 2783", func() { Expect(book.Pages).To(Equal(2783)) }) // flaky under randomization
})
```

The same trap explains: **no assertions in container bodies** (they fire at construction, with no spec active) and **no expensive/stateful work in container bodies**. If you see logic directly inside a `Describe`/`Context`/`When` body, it almost always belongs in a `BeforeEach`.

The rest: https://github.com/onsi/ginkgo/blob/HEAD/plugins/ginkgo/skills/writing-specs/SKILL.md
