Table-driven testing allows you to define a single test with multiple sets of inputs and expected outputs. Ginkgo generates a separate spec for each table entry.
Creates a table of specs. Each entry generates a separate spec.
func DescribeTable(description string, args ...any) boolParameters:
description: Table description (can include %s, %d, etc. for entry substitution)args: Test body function, table entries, and optional decoratorsExample:
DescribeTable("addition",
func(a, b, expected int) {
result := a + b
Expect(result).To(Equal(expected))
},
Entry("1 + 1", 1, 1, 2),
Entry("2 + 3", 2, 3, 5),
Entry("negative numbers", -1, -2, -3),
Entry("zero", 0, 0, 0),
)Focused table - only specs in focused tables run.
func FDescribeTable(description string, args ...any) boolPending table - marks all table entries as pending.
func PDescribeTable(description string, args ...any) boolAlias for PDescribeTable.
var XDescribeTable = PDescribeTableCreates a table where each entry generates a subtree of specs instead of a single spec.
func DescribeTableSubtree(description string, args ...any) boolExample:
DescribeTableSubtree("HTTP methods",
func(method string) {
It("returns 200", func() {
response := makeRequest(method, "/api/users")
Expect(response.Status).To(Equal(200))
})
It("includes proper headers", func() {
response := makeRequest(method, "/api/users")
Expect(response.Headers).To(HaveKey("Content-Type"))
})
},
Entry("GET", "GET"),
Entry("POST", "POST"),
Entry("PUT", "PUT"),
)
// Generates: "HTTP methods GET returns 200", "HTTP methods GET includes proper headers",
// "HTTP methods POST returns 200", etc.Focused table subtree.
func FDescribeTableSubtree(description string, args ...any) boolPending table subtree.
func PDescribeTableSubtree(description string, args ...any) boolAlias for PDescribeTableSubtree.
var XDescribeTableSubtree = PDescribeTableSubtreeCreates a table entry with description and parameters.
func Entry(description any, args ...any) TableEntry
type TableEntry struct {
Description string
Parameters []any
Decorations []any
CodeLocation CodeLocation
}Parameters:
description: Entry description (string or EntryDescription function)args: Parameters passed to test function, followed by optional decoratorsExample:
DescribeTable("validating inputs",
func(input string, shouldBeValid bool) {
Expect(IsValid(input)).To(Equal(shouldBeValid))
},
Entry("valid email", "test@example.com", true),
Entry("invalid email", "not-an-email", false),
Entry("empty string", "", false),
)Focused entry - only focused entries in a table run.
func FEntry(description any, args ...any) TableEntryExample:
DescribeTable("math operations",
func(a, b, expected int) {
Expect(a + b).To(Equal(expected))
},
Entry("regular", 1, 2, 3),
FEntry("debugging this", 5, 7, 12), // Only this entry runs
Entry("another", 3, 4, 7),
)Pending entry - marks entry as pending (skipped).
func PEntry(description any, args ...any) TableEntryExample:
DescribeTable("features",
func(feature string) {
Expect(IsImplemented(feature)).To(BeTrue())
},
Entry("feature A", "featureA"),
PEntry("not implemented yet", "featureB"), // Skipped
Entry("feature C", "featureC"),
)Alias for PEntry.
var XEntry = PEntryFunction type for dynamically generating entry descriptions from parameters.
type EntryDescription func(...any) stringExample:
DescribeTable("operations",
func(op string, a, b, result int) {
Expect(Calculate(op, a, b)).To(Equal(result))
},
Entry(func(op string, a, b, result int) string {
return fmt.Sprintf("%d %s %d = %d", a, op, b, result)
}, "+", 1, 2, 3), // Generates: "1 + 2 = 3"
Entry(func(op string, a, b, result int) string {
return fmt.Sprintf("%d %s %d = %d", a, op, b, result)
}, "*", 3, 4, 12), // Generates: "3 * 4 = 12"
)Decorators can be applied to entire tables or individual entries:
DescribeTable("database operations",
func(query string) {
result := db.Execute(query)
Expect(result).NotTo(BeNil())
},
Entry("SELECT", "SELECT * FROM users"),
Entry("INSERT", "INSERT INTO users VALUES (...)"),
Label("database", "integration"), // Applies to all entries
Serial, // All entries run serially
)DescribeTable("API endpoints",
func(endpoint string) {
response := callAPI(endpoint)
Expect(response.Status).To(Equal(200))
},
Entry("fast endpoint", "/api/quick", NodeTimeout(1*time.Second)),
Entry("slow endpoint", "/api/slow", NodeTimeout(10*time.Second)),
Entry("flaky endpoint", "/api/flaky", FlakeAttempts(3)),
Entry("critical", "/api/critical", Label("smoke")),
)type TestCase struct {
Input string
Expected int
ShouldErr bool
}
DescribeTable("parsing",
func(tc TestCase) {
result, err := Parse(tc.Input)
if tc.ShouldErr {
Expect(err).To(HaveOccurred())
} else {
Expect(err).NotTo(HaveOccurred())
Expect(result).To(Equal(tc.Expected))
}
},
Entry("valid input", TestCase{
Input: "42",
Expected: 42,
ShouldErr: false,
}),
Entry("invalid input", TestCase{
Input: "not-a-number",
Expected: 0,
ShouldErr: true,
}),
)DescribeTable("async operations",
func(ctx SpecContext, operation string) {
result := make(chan string, 1)
go performAsync(ctx, operation, result)
Eventually(ctx, result).Should(Receive())
},
Entry("operation A", "opA"),
Entry("operation B", "opB"),
)Use format strings in table descriptions:
DescribeTable("testing %s with %d items",
func(feature string, count int) {
result := ProcessItems(feature, count)
Expect(result).To(BeTrue())
},
Entry(nil, "caching", 10), // Description: "testing caching with 10 items"
Entry(nil, "validation", 5), // Description: "testing validation with 5 items"
Entry(nil, "persistence", 100), // Description: "testing persistence with 100 items"
)When passing nil as the first argument to Entry, Ginkgo uses the table description as a format string.
Describe("Calculator", func() {
DescribeTable("addition",
func(a, b, expected int) {
Expect(a + b).To(Equal(expected))
},
Entry("positive", 1, 2, 3),
Entry("negative", -1, -2, -3),
)
DescribeTable("multiplication",
func(a, b, expected int) {
Expect(a * b).To(Equal(expected))
},
Entry("positive", 2, 3, 6),
Entry("with zero", 5, 0, 0),
)
})Describe("User operations", func() {
var user *User
BeforeEach(func() {
user = &User{}
})
DescribeTable("setting properties",
func(property, value string) {
err := user.Set(property, value)
Expect(err).NotTo(HaveOccurred())
Expect(user.Get(property)).To(Equal(value))
},
Entry("name", "name", "John"),
Entry("email", "email", "john@example.com"),
Entry("role", "role", "admin"),
)
})DescribeTable("email validation",
func(email string, valid bool) {
result := ValidateEmail(email)
Expect(result).To(Equal(valid))
},
// Valid emails
Entry("standard", "user@example.com", true),
Entry("with plus", "user+tag@example.com", true),
Entry("with subdomain", "user@mail.example.com", true),
// Invalid emails
Entry("no @", "userexample.com", false),
Entry("no domain", "user@", false),
Entry("no local", "@example.com", false),
Entry("empty", "", false),
)// Good: Descriptive names
Entry("handles empty input gracefully", "", ExpectedBehavior{...})
Entry("rejects negative numbers", -1, ExpectedBehavior{...})
// Bad: Non-descriptive names
Entry("test 1", "", ExpectedBehavior{...})
Entry("test 2", -1, ExpectedBehavior{...})// Good: Simple, focused test function
DescribeTable("string operations",
func(input, expected string) {
result := ToUpper(input)
Expect(result).To(Equal(expected))
},
Entry("lowercase", "hello", "HELLO"),
Entry("mixed", "HeLLo", "HELLO"),
)
// If test logic gets complex, break into separate teststype ValidationTest struct {
Input string
Expected ValidationResult
}
DescribeTable("validation",
func(test ValidationTest) {
result := Validate(test.Input)
Expect(result).To(Equal(test.Expected))
},
Entry("valid", ValidationTest{
Input: "valid-input",
Expected: ValidationResult{Valid: true},
}),
)