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.
The suite package enables creating testing suites where you can:
The suite package does not support parallel tests. See issue 934.
import "github.com/stretchr/testify/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()) boolThe Suite struct embeds *assert.Assertions, so all assert methods are available directly on the suite (e.g., suite.Equal(expected, actual)).
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.
Test suites can implement these interfaces to add setup and teardown behavior at different points in the test lifecycle.
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:
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:
Called before each individual test method runs.
type SetupTestSuite interface {
SetupTest()
}Usage: Implement this interface to reset state before each test, such as:
Called after each individual test method completes.
type TearDownTestSuite interface {
TearDownTest()
}Usage: Implement this interface to clean up after each test, such as:
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 executedUsage: Implement this interface when you need test-specific setup that depends on knowing which test is running.
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 executedUsage: Implement this interface when you need test-specific cleanup that depends on knowing which test ran.
Called before each subtest runs.
type SetupSubTest interface {
SetupSubTest()
}Usage: Implement this interface to initialize state before subtests created with suite.Run().
Called after each subtest completes.
type TearDownSubTest interface {
TearDownSubTest()
}Usage: Implement this interface to clean up after subtests created with suite.Run().
Called after the suite completes with execution statistics.
type WithStats interface {
HandleStats(suiteName string, stats *SuiteInformation)
}Parameters:
suiteName: The name of the test suitestats: Statistics about suite execution (see SuiteInformation)Usage: Implement this interface to collect metrics, generate reports, or log suite execution details.
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 functionsuite: 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:
Test methods are identified by having a name that starts with "Test" and taking no parameters.
Contains statistics about suite execution.
type SuiteInformation struct {
Start, End time.Time
TestStats map[string]*TestInformation
}Fields:
Start: When the suite started executingEnd: When the suite finished executingTestStats: Map of test names to their execution informationMethods:
func (s *SuiteInformation) Passed() boolReturns true if all tests in the suite passed.
Contains statistics about individual test execution.
type TestInformation struct {
TestName string
Start, End time.Time
Passed bool
}Fields:
TestName: The name of the test methodStart: When the test started executingEnd: When the test finished executingPassed: Whether the test passedA 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))
}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))
}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))
}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))
}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))
}When running a suite, the execution order is:
Suite Setup (once per suite)
SetupSuite() - if SetupAllSuite implementedFor each test method:
SetupTest() - if SetupTestSuite implementedBeforeTest(suiteName, testName) - if BeforeTest implementedAfterTest(suiteName, testName) - if AfterTest implementedTearDownTest() - if TearDownTestSuite implementedSuite Teardown (once per suite)
TearDownSuite() - if TearDownAllSuite implementedHandleStats(suiteName, stats) - if WithStats implementedOnly 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
}The -run flag filters which test suites run:
go test -run TestMySuiteThe -m flag (if supported by your test runner) can filter test methods within a suite:
go test -run TestMySuite -m TestSpecificMethodThe suite package does not support parallel test execution. Do not call t.Parallel() in suite tests.
s.Equal() or s.Assert().Equal() to continue test execution after assertion failures.Require().Equal() to stop test execution immediately on assertion failureExample:
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")
}Each test method runs on the same suite instance, but:
SetupTest() should reset state between testsWhen using suite.Run() for subtests:
suite.Run() instead of t.Run() to maintain suite context*testing.T contextsSetupSubTest() and TearDownSubTest() hooks are availablego test -run TestSuite/TestMethod/SubTestName