or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

configuration.mdcore-dsl.mddecorators.mdextensions.mdindex.mdreporters.mdreporting.mdtable-driven-testing.mdtypes.md
tile.json

table-driven-testing.mddocs/

Table-Driven Testing

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.

Table Functions

DescribeTable

Creates a table of specs. Each entry generates a separate spec.

func DescribeTable(description string, args ...any) bool

Parameters:

  • description: Table description (can include %s, %d, etc. for entry substitution)
  • args: Test body function, table entries, and optional decorators

Example:

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),
)

FDescribeTable

Focused table - only specs in focused tables run.

func FDescribeTable(description string, args ...any) bool

PDescribeTable

Pending table - marks all table entries as pending.

func PDescribeTable(description string, args ...any) bool

XDescribeTable

Alias for PDescribeTable.

var XDescribeTable = PDescribeTable

DescribeTableSubtree

Creates a table where each entry generates a subtree of specs instead of a single spec.

func DescribeTableSubtree(description string, args ...any) bool

Example:

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.

FDescribeTableSubtree

Focused table subtree.

func FDescribeTableSubtree(description string, args ...any) bool

PDescribeTableSubtree

Pending table subtree.

func PDescribeTableSubtree(description string, args ...any) bool

XDescribeTableSubtree

Alias for PDescribeTableSubtree.

var XDescribeTableSubtree = PDescribeTableSubtree

Table Entries

Entry

Creates 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 decorators

Example:

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),
)

FEntry

Focused entry - only focused entries in a table run.

func FEntry(description any, args ...any) TableEntry

Example:

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),
)

PEntry

Pending entry - marks entry as pending (skipped).

func PEntry(description any, args ...any) TableEntry

Example:

DescribeTable("features",
    func(feature string) {
        Expect(IsImplemented(feature)).To(BeTrue())
    },
    Entry("feature A", "featureA"),
    PEntry("not implemented yet", "featureB"), // Skipped
    Entry("feature C", "featureC"),
)

XEntry

Alias for PEntry.

var XEntry = PEntry

Entry Descriptions

EntryDescription

Function type for dynamically generating entry descriptions from parameters.

type EntryDescription func(...any) string

Example:

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"
)

Using Decorators with Tables

Decorators can be applied to entire tables or individual entries:

Table-Level Decorators

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
)

Entry-Level Decorators

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")),
)

Advanced Table Patterns

Complex Parameter Types

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,
    }),
)

Using SpecContext

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"),
)

Parameterized Descriptions

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.

Nested Tables

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),
    )
})

Combining with BeforeEach

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"),
    )
})

Table-Driven Testing Best Practices

Group Related Test Cases

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),
)

Use Descriptive Entry Names

// 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{...})

Keep Test Functions Simple

// 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 tests

Use Type-Safe Parameters

type 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},
    }),
)