Go Test Helper
Let me explain the considerations for using t.Helper()
with mock setup functions.
Generally, you should use t.Helper()
in mock setup functions if they perform assertions or could fail in a way that needs to be reported in tests. Here’s a detailed breakdown with examples:
- Mock setup with potential failures - Should use t.Helper():
func setupMockDB(t *testing.T) *sql.DB {
t.Helper()
db, err := sql.Open("mock", "mock_connection")
if err != nil {
t.Fatalf("failed to setup mock DB: %v", err)
}
return db
}
- Mock setup with assertions - Should use t.Helper():
func setupUserServiceMock(t *testing.T) *mockUserService {
t.Helper()
mock := &mockUserService{}
if err := mock.Validate(); err != nil {
t.Fatalf("invalid mock configuration: %v", err)
}
return mock
}
- Simple mock creation without failures - No t.Helper() needed:
func newMockRepository() *mockRepository {
return &mockRepository{
users: make(map[string]User),
}
}
Here’s a more comprehensive example showing both cases:
type MockHTTPClient struct {
responses map[string]*http.Response
calls []string
}
// No t.Helper() needed - pure creation without testing logic
func NewMockHTTPClient() *MockHTTPClient {
return &MockHTTPClient{
responses: make(map[string]*http.Response),
calls: make([]string, 0),
}
}
// Uses t.Helper() because it contains assertions and can fail
func setupMockHTTPClientWithResponses(t *testing.T, responses map[string]string) *MockHTTPClient {
t.Helper()
client := NewMockHTTPClient()
for path, body := range responses {
resp := &http.Response{
Body: io.NopCloser(strings.NewReader(body)),
}
if body == "" {
t.Fatalf("empty response body for path: %s", path)
}
client.responses[path] = resp
}
return client
}
func TestUserService(t *testing.T) {
// Simple mock - no helper needed
mockRepo := newMockRepository()
// Complex mock with potential failures - uses helper
mockHTTP := setupMockHTTPClientWithResponses(t, map[string]string{
"/users": `{"users": []}`,
"/auth": `{"token": "123"}`,
})
service := NewUserService(mockRepo, mockHTTP)
// ... rest of the test
}
Here’s an example with a more complex mock that shows when to use and not use t.Helper()
:
type MockEmailSender struct {
mu sync.Mutex
sent []Email
failing bool
}
// No t.Helper() needed - simple creation
func NewMockEmailSender() *MockEmailSender {
return &MockEmailSender{
sent: make([]Email, 0),
}
}
// Uses t.Helper() because it sets up test conditions and validates
func setupEmailSenderWithFailure(t *testing.T, shouldFail bool) *MockEmailSender {
t.Helper()
sender := NewMockEmailSender()
sender.failing = shouldFail
// Verify initial state
if len(sender.sent) != 0 {
t.Fatal("new mock email sender should start with empty sent slice")
}
return sender
}
// Helper function for assertions - uses t.Helper()
func assertEmailSent(t *testing.T, sender *MockEmailSender, expected Email) {
t.Helper()
sender.mu.Lock()
defer sender.mu.Unlock()
for _, email := range sender.sent {
if email.To == expected.To && email.Subject == expected.Subject {
return
}
}
t.Errorf("expected email not sent: %+v", expected)
}
func TestEmailNotification(t *testing.T) {
// Simple mock creation - no helper needed
sender := NewMockEmailSender()
// Setup with test conditions - uses helper
failingSender := setupEmailSenderWithFailure(t, true)
// Test with normal sender
notification := NewNotificationService(sender)
err := notification.SendWelcomeEmail("[email protected]")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
// Assert using helper
assertEmailSent(t, sender, Email{
To: "[email protected]",
Subject: "Welcome!",
})
// Test with failing sender
failingNotification := NewNotificationService(failingSender)
if err := failingNotification.SendWelcomeEmail("[email protected]"); err == nil {
t.Error("expected error from failing sender")
}
}
Guidelines for when to use t.Helper()
in mock-related functions:
- Use t.Helper() when the function:
- Takes a
*testing.T
parameter - Performs assertions
- Can fail and report test errors
- Sets up complex test conditions that need validation
- Configures mocks with test-specific behavior
- Don’t use t.Helper() when the function:
- Simply creates a new mock instance
- Doesn’t take a
*testing.T
parameter - Doesn’t perform any assertions or error reporting
- Is used outside of tests
- Only sets up basic state without validation
Remember that t.Helper()
is about improving test failure reporting, so use it when the function’s failure should be attributed to the calling test rather than the helper function itself.