or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

assert.mdhttp.mdindex.mdmock.mdrequire.mdsuite.md
tile.json

suite.mddocs/

Suite Package

The suite package provides a structured test suite framework with setup/teardown hooks and lifecycle management. It allows organizing related tests into cohesive suites with shared state and common initialization/cleanup logic.

Package Overview

The suite package enables creating testing suites where you can:

  • Group related tests together in a struct
  • Define setup and teardown hooks at suite and test levels
  • Access test metadata (suite name, test name) in lifecycle hooks
  • Collect suite execution statistics
  • Use integrated assert and require functionality
  • Run subtests with suite context

The suite package does not support parallel tests. See issue 934.

Import

import "github.com/stretchr/testify/suite"

Core Types

Suite

The base struct that should be embedded in your test suites. It provides the foundation for suite functionality.

type Suite struct {
    *assert.Assertions
    // Has unexported fields
}

Methods:

// T retrieves the current *testing.T context
func (suite *Suite) T() *testing.T

// SetT sets the current *testing.T context
func (suite *Suite) SetT(t *testing.T)

// SetS sets the current test suite as parent to get access to parent methods
func (suite *Suite) SetS(s TestingSuite)

// Assert returns an assert context for suite
func (suite *Suite) Assert() *assert.Assertions

// Require returns a require context for suite
func (suite *Suite) Require() *require.Assertions

// Run provides suite functionality around golang subtests
func (suite *Suite) Run(name string, subtest func()) bool

The Suite struct embeds *assert.Assertions, so all assert methods are available directly on the suite (e.g., suite.Equal(expected, actual)).

TestingSuite

The core interface that test suites must implement. The Suite struct implements this interface.

type TestingSuite interface {
    T() *testing.T
    SetT(*testing.T)
    SetS(suite TestingSuite)
}

This interface allows the suite framework to manage the *testing.T context across test execution.

Lifecycle Hook Interfaces

Test suites can implement these interfaces to add setup and teardown behavior at different points in the test lifecycle.

SetupAllSuite

Called once before any tests in the suite run.

type SetupAllSuite interface {
    SetupSuite()
}

Usage: Implement this interface to perform one-time initialization for the entire suite, such as:

  • Setting up database connections
  • Starting test servers
  • Initializing shared resources

TearDownAllSuite

Called once after all tests in the suite have completed.

type TearDownAllSuite interface {
    TearDownSuite()
}

Usage: Implement this interface to perform one-time cleanup after all tests, such as:

  • Closing database connections
  • Stopping test servers
  • Cleaning up shared resources

SetupTestSuite

Called before each individual test method runs.

type SetupTestSuite interface {
    SetupTest()
}

Usage: Implement this interface to reset state before each test, such as:

  • Resetting test data
  • Clearing caches
  • Initializing per-test resources

TearDownTestSuite

Called after each individual test method completes.

type TearDownTestSuite interface {
    TearDownTest()
}

Usage: Implement this interface to clean up after each test, such as:

  • Removing temporary files
  • Resetting mocked dependencies
  • Clearing per-test state

BeforeTest

Called before each test with metadata about the test being run.

type BeforeTest interface {
    BeforeTest(suiteName, testName string)
}

Parameters:

  • suiteName: The name of the test suite (struct type name)
  • testName: The name of the test method being executed

Usage: Implement this interface when you need test-specific setup that depends on knowing which test is running.

AfterTest

Called after each test with metadata about the test that ran.

type AfterTest interface {
    AfterTest(suiteName, testName string)
}

Parameters:

  • suiteName: The name of the test suite (struct type name)
  • testName: The name of the test method that executed

Usage: Implement this interface when you need test-specific cleanup that depends on knowing which test ran.

SetupSubTest

Called before each subtest runs.

type SetupSubTest interface {
    SetupSubTest()
}

Usage: Implement this interface to initialize state before subtests created with suite.Run().

TearDownSubTest

Called after each subtest completes.

type TearDownSubTest interface {
    TearDownSubTest()
}

Usage: Implement this interface to clean up after subtests created with suite.Run().

WithStats

Called after the suite completes with execution statistics.

type WithStats interface {
    HandleStats(suiteName string, stats *SuiteInformation)
}

Parameters:

  • suiteName: The name of the test suite
  • stats: Statistics about suite execution (see SuiteInformation)

Usage: Implement this interface to collect metrics, generate reports, or log suite execution details.

Running Suites

Run Function

Executes a test suite, running all test methods and invoking lifecycle hooks.

func Run(t *testing.T, suite TestingSuite)

Parameters:

  • t: The *testing.T context from a standard Go test function
  • suite: The test suite to run (must implement TestingSuite interface)

Usage: Call this function from a normal test function that matches the pattern func Test*(t *testing.T). The suite framework will:

  1. Execute SetupSuite() if implemented
  2. For each test method (functions starting with "Test"):
    • Execute SetupTest() if implemented
    • Execute BeforeTest() if implemented
    • Run the test method
    • Execute AfterTest() if implemented
    • Execute TearDownTest() if implemented
  3. Execute TearDownSuite() if implemented
  4. Execute HandleStats() if implemented

Test methods are identified by having a name that starts with "Test" and taking no parameters.

Statistics Types

SuiteInformation

Contains statistics about suite execution.

type SuiteInformation struct {
    Start, End time.Time
    TestStats  map[string]*TestInformation
}

Fields:

  • Start: When the suite started executing
  • End: When the suite finished executing
  • TestStats: Map of test names to their execution information

Methods:

func (s *SuiteInformation) Passed() bool

Returns true if all tests in the suite passed.

TestInformation

Contains statistics about individual test execution.

type TestInformation struct {
    TestName   string
    Start, End time.Time
    Passed     bool
}

Fields:

  • TestName: The name of the test method
  • Start: When the test started executing
  • End: When the test finished executing
  • Passed: Whether the test passed

Usage Examples

Basic Suite

A simple test suite with setup and test methods:

package mypackage

import (
    "testing"
    "github.com/stretchr/testify/suite"
)

// Define the suite by embedding suite.Suite
type BasicSuite struct {
    suite.Suite
    testValue int
}

// SetupTest runs before each test
func (s *BasicSuite) SetupTest() {
    s.testValue = 42
}

// Test methods start with "Test"
func (s *BasicSuite) TestValue() {
    s.Equal(42, s.testValue)
}

func (s *BasicSuite) TestAssertions() {
    s.NotNil(s.T())
    s.True(true)
}

// Hook up the suite to Go's test runner
func TestBasicSuite(t *testing.T) {
    suite.Run(t, new(BasicSuite))
}

Suite with Full Lifecycle

A suite implementing all lifecycle hooks:

package mypackage

import (
    "database/sql"
    "testing"
    "github.com/stretchr/testify/suite"
)

type FullLifecycleSuite struct {
    suite.Suite
    db        *sql.DB
    txn       *sql.Tx
    testCount int
}

// SetupSuite runs once before all tests
func (s *FullLifecycleSuite) SetupSuite() {
    var err error
    s.db, err = sql.Open("postgres", "test_connection_string")
    s.Require().NoError(err)
    s.testCount = 0
}

// TearDownSuite runs once after all tests
func (s *FullLifecycleSuite) TearDownSuite() {
    if s.db != nil {
        s.db.Close()
    }
}

// SetupTest runs before each test
func (s *FullLifecycleSuite) SetupTest() {
    var err error
    s.txn, err = s.db.Begin()
    s.Require().NoError(err)
}

// TearDownTest runs after each test
func (s *FullLifecycleSuite) TearDownTest() {
    if s.txn != nil {
        s.txn.Rollback()
    }
}

// BeforeTest runs before each test with metadata
func (s *FullLifecycleSuite) BeforeTest(suiteName, testName string) {
    s.T().Logf("Starting test: %s.%s", suiteName, testName)
    s.testCount++
}

// AfterTest runs after each test with metadata
func (s *FullLifecycleSuite) AfterTest(suiteName, testName string) {
    s.T().Logf("Finished test: %s.%s", suiteName, testName)
}

// HandleStats receives execution statistics
func (s *FullLifecycleSuite) HandleStats(suiteName string, stats *suite.SuiteInformation) {
    s.T().Logf("Suite %s completed", suiteName)
    s.T().Logf("Total tests run: %d", s.testCount)
    s.T().Logf("Duration: %v", stats.End.Sub(stats.Start))

    for testName, info := range stats.TestStats {
        status := "PASS"
        if !info.Passed {
            status = "FAIL"
        }
        s.T().Logf("  %s: %s (%v)", testName, status, info.End.Sub(info.Start))
    }
}

// Test methods
func (s *FullLifecycleSuite) TestDatabaseQuery() {
    rows, err := s.txn.Query("SELECT 1")
    s.NoError(err)
    defer rows.Close()
    s.True(rows.Next())
}

func (s *FullLifecycleSuite) TestAnotherOperation() {
    s.NotNil(s.txn)
    s.NotNil(s.db)
}

func TestFullLifecycleSuite(t *testing.T) {
    suite.Run(t, new(FullLifecycleSuite))
}

Suite with Subtests

Using suite.Run() for subtests:

package mypackage

import (
    "testing"
    "github.com/stretchr/testify/suite"
)

type SubtestSuite struct {
    suite.Suite
    counter int
}

func (s *SubtestSuite) SetupSubTest() {
    s.counter = 0
}

func (s *SubtestSuite) TearDownSubTest() {
    s.T().Logf("Counter after subtest: %d", s.counter)
}

func (s *SubtestSuite) TestWithSubtests() {
    testCases := []struct {
        name     string
        input    int
        expected int
    }{
        {"add one", 1, 1},
        {"add five", 5, 5},
        {"add ten", 10, 10},
    }

    for _, tc := range testCases {
        s.Run(tc.name, func() {
            s.counter += tc.input
            s.Equal(tc.expected, s.counter)
        })
    }
}

func TestSubtestSuite(t *testing.T) {
    suite.Run(t, new(SubtestSuite))
}

Using Assert and Require

Suites provide direct access to both assert and require functionality:

package mypackage

import (
    "errors"
    "testing"
    "github.com/stretchr/testify/suite"
)

type AssertionsSuite struct {
    suite.Suite
}

func (s *AssertionsSuite) TestDirectAssertions() {
    // Direct use of embedded assert methods
    s.Equal(42, 42)
    s.NotNil(s.T())
    s.True(true)
}

func (s *AssertionsSuite) TestExplicitAssert() {
    // Explicit assert context
    s.Assert().Equal(42, 42)
    s.Assert().True(true)
}

func (s *AssertionsSuite) TestRequire() {
    // Require stops test on failure
    s.Require().NotNil(s.T())

    // This would stop the test if err was not nil
    err := someFunction()
    s.Require().NoError(err)

    // Code after Require() only runs if assertion passed
    s.T().Log("Test continues after require")
}

func someFunction() error {
    return nil
}

func TestAssertionsSuite(t *testing.T) {
    suite.Run(t, new(AssertionsSuite))
}

Statistics Collection Example

Collecting and analyzing suite statistics:

package mypackage

import (
    "testing"
    "time"
    "github.com/stretchr/testify/suite"
)

type StatsSuite struct {
    suite.Suite
    slowTests []string
}

func (s *StatsSuite) HandleStats(suiteName string, stats *suite.SuiteInformation) {
    threshold := 100 * time.Millisecond

    s.slowTests = []string{}
    for testName, info := range stats.TestStats {
        duration := info.End.Sub(info.Start)
        if duration > threshold {
            s.slowTests = append(s.slowTests, testName)
            s.T().Logf("SLOW TEST: %s took %v", testName, duration)
        }
    }

    if len(s.slowTests) > 0 {
        s.T().Logf("Found %d slow tests", len(s.slowTests))
    }

    totalDuration := stats.End.Sub(stats.Start)
    s.T().Logf("Suite completed in %v", totalDuration)

    if !stats.Passed() {
        s.T().Log("Suite had failures")
    }
}

func (s *StatsSuite) TestFastOperation() {
    time.Sleep(10 * time.Millisecond)
    s.True(true)
}

func (s *StatsSuite) TestSlowOperation() {
    time.Sleep(150 * time.Millisecond)
    s.True(true)
}

func TestStatsSuite(t *testing.T) {
    suite.Run(t, new(StatsSuite))
}

Suite Lifecycle Order

When running a suite, the execution order is:

  1. Suite Setup (once per suite)

    • SetupSuite() - if SetupAllSuite implemented
  2. For each test method:

    • SetupTest() - if SetupTestSuite implemented
    • BeforeTest(suiteName, testName) - if BeforeTest implemented
    • Test method execution
    • AfterTest(suiteName, testName) - if AfterTest implemented
    • TearDownTest() - if TearDownTestSuite implemented
  3. Suite Teardown (once per suite)

    • TearDownSuite() - if TearDownAllSuite implemented
    • HandleStats(suiteName, stats) - if WithStats implemented

Notes and Best Practices

Test Method Naming

Only methods that start with "Test" are executed as tests. Other methods are ignored and can be used as helper functions:

type MySuite struct {
    suite.Suite
}

// This runs as a test
func (s *MySuite) TestSomething() {
    s.helperMethod()
}

// This is a helper method, not a test
func (s *MySuite) helperMethod() {
    // Helper logic
}

Command-Line Filtering

The -run flag filters which test suites run:

go test -run TestMySuite

The -m flag (if supported by your test runner) can filter test methods within a suite:

go test -run TestMySuite -m TestSpecificMethod

Parallel Execution

The suite package does not support parallel test execution. Do not call t.Parallel() in suite tests.

Assert vs Require

  • Use s.Equal() or s.Assert().Equal() to continue test execution after assertion failure
  • Use s.Require().Equal() to stop test execution immediately on assertion failure

Example:

func (s *MySuite) TestValidation() {
    // Require stops test if validation fails
    s.Require().NotNil(s.db)

    // Assert allows test to continue
    result := s.db.Query("SELECT 1")
    s.Assert().NoError(result.Error)

    // This line runs even if the assert above fails
    s.T().Log("Query attempted")
}

State Management

Each test method runs on the same suite instance, but:

  • SetupTest() should reset state between tests
  • Use transaction rollback pattern for database tests
  • Avoid test interdependencies

Subtests with suite.Run()

When using suite.Run() for subtests:

  • Call suite.Run() instead of t.Run() to maintain suite context
  • Subtests get fresh *testing.T contexts
  • SetupSubTest() and TearDownSubTest() hooks are available
  • Compatible with go test -run TestSuite/TestMethod/SubTestName