skill-testing-standards
Enforces testing standards like avoiding `time.Sleep` and using table-driven tests.
Install
mkdir -p .claude/skills/skill-testing-standards && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/14432" && unzip -o skill.zip -d .claude/skills/skill-testing-standards && rm skill.zipInstalls to .claude/skills/skill-testing-standards
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Testing standards and best practices for Meridian servicesKey capabilities
- →Use the `await` package for asynchronous assertions
- →Implement defensive testing for happy and unhappy paths
- →Structure tests using table-driven patterns with rationale
- →Ensure test isolation to prevent dependencies
- →Utilize Testcontainers for database integration tests
How it works
This skill provides guidelines and examples for writing Go tests, emphasizing the use of the `await` package for async operations and Testcontainers for database tests. It also promotes defensive and table-driven testing.
Inputs & outputs
When to use skill-testing-standards
- →Refactoring flaky async tests
- →Implementing table-driven database tests
- →Validating test coverage
About this skill
Testing Standards
This document defines testing standards for Meridian services.
Core Principles
- No
time.Sleep- Use theawaitpackage for async assertions - Defensive testing - Test happy paths AND unhappy paths (see ADR-0008)
- Table-driven tests - Use Go table-driven patterns with rationale
- Isolation - Tests must not depend on each other
- Fast feedback - Unit tests should complete in milliseconds
Async Testing: Use await Not time.Sleep
NEVER use time.Sleep in tests. It creates flaky tests - too short causes failures, too long wastes CI time.
Use shared/platform/await instead. It polls until conditions are met or timeout.
import "github.com/meridianhub/meridian/shared/platform/await"
// BAD - arbitrary sleep, flaky and slow
time.Sleep(2 * time.Second)
assert.Equal(t, "COMPLETED", order.Status)
// GOOD - polls until condition met or timeout
err := await.Until(func() bool {
return order.Status == "COMPLETED"
})
require.NoError(t, err)
// With custom timeout and poll interval
err := await.New().
AtMost(5 * time.Second).
PollInterval(50 * time.Millisecond).
Until(func() bool {
return repo.FindByID(ctx, id) != nil
})
// Wait for an operation to succeed (no error)
err := await.UntilNoError(func() error {
return client.HealthCheck()
})
// With context cancellation
err := await.New().
WithContext(ctx).
AtMost(10 * time.Second).
Until(condition)
Defaults: 10s timeout, 100ms poll interval.
For advanced matchers and more expressive assertions, consider gomega.Eventually().
Database Integration Tests: Use Testcontainers
Use shared/platform/testdb/ for PostgreSQL integration tests. Each test gets an isolated container.
import "github.com/meridianhub/meridian/internal/<service>/repository/testhelpers"
func TestRepository(t *testing.T) {
tc := testhelpers.SetupTestContainer(t)
defer tc.Cleanup(t) // CRITICAL - always defer cleanup
// Use tc.Repo for repository operations
err := tc.Repo.Create(ctx, entity)
require.NoError(t, err)
// Or use tc.Pool for direct SQL
var count int
err = tc.Pool.QueryRow(ctx, "SELECT COUNT(*) FROM table").Scan(&count)
require.NoError(t, err)
}
See Testcontainers Usage Guide for detailed patterns.
Table-Driven Tests with Rationale
Every test case must include a rationale field explaining WHY it matters:
func TestDeposit(t *testing.T) {
tests := []struct {
name string
amount int64
wantErr bool
rationale string // WHY this test case matters
}{
// Happy path
{
name: "valid deposit",
amount: 100,
wantErr: false,
rationale: "Standard valid deposit should succeed",
},
// Edge cases
{
name: "minimum valid amount",
amount: 1,
wantErr: false,
rationale: "Even 1 cent is a valid deposit",
},
// Unhappy paths
{
name: "zero amount rejected",
amount: 0,
wantErr: true,
rationale: "Zero deposits are meaningless operations",
},
{
name: "negative amount rejected",
amount: -100,
wantErr: true,
rationale: "Negative deposits don't make sense (use withdraw)",
},
// Defensive: values that shouldn't happen but might
{
name: "overflow detection",
amount: math.MaxInt64,
wantErr: true,
rationale: "Must detect arithmetic overflow to prevent corruption",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := account.Deposit(tt.amount)
if tt.wantErr {
assert.Error(t, err, tt.rationale)
} else {
assert.NoError(t, err, tt.rationale)
}
})
}
}
Defensive Testing Categories
Apply these categories to all tests (see ADR-0008):
For Numeric Inputs
- Valid values (happy path)
- Zero
- Negative values
- Maximum/minimum boundaries
- Overflow conditions
For String Inputs
- Valid formatted strings
- Empty strings
- Whitespace-only strings
- Invalid formats
- Very long strings
For State Transitions
- Valid transitions
- Invalid transitions (must be rejected)
- Idempotent operations
- Concurrent state changes
For Error Handling
- Every error path has a test
- State is not modified on error
- Resources are cleaned up on error
- Errors wrap correctly (
errors.Is/errors.Aswork)
Test Assertions: Use testify
Use github.com/stretchr/testify for assertions:
import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// Use require for setup - fails fast
tc := testhelpers.SetupTestContainer(t)
require.NotNil(t, tc.Pool, "database pool required")
// Use assert for verifications - continues on failure
assert.Equal(t, expected, actual)
assert.NoError(t, err)
assert.ErrorIs(t, err, ErrNotFound)
assert.Contains(t, logs, "expected message")
require vs assert:
require- Stops test immediately on failure (use for setup/preconditions)assert- Records failure but continues (use for verifications)
Test Naming
Use descriptive names that explain the scenario:
// GOOD - describes scenario and expected outcome
func TestDeposit_ZeroAmount_ReturnsError(t *testing.T)
func TestFindByID_NonExistent_ReturnsNotFoundError(t *testing.T)
func TestTransfer_InsufficientFunds_RollsBackTransaction(t *testing.T)
// BAD - vague names
func TestDeposit1(t *testing.T)
func TestError(t *testing.T)
func TestIt(t *testing.T)
Parallel Tests
Run independent tests in parallel for faster feedback:
func TestConcurrentOperations(t *testing.T) {
t.Parallel() // Mark test as safe for parallel execution
tc := testhelpers.SetupTestContainer(t)
defer tc.Cleanup(t)
// Each parallel test gets its own container
}
When NOT to parallelize:
- Tests sharing global state
- Tests with fixed port bindings
- Tests modifying environment variables
Mocking Guidelines
Minimize mocking. Prefer real implementations where practical:
- Use testcontainers for database tests (real PostgreSQL)
- Use in-memory implementations for simple interfaces
- Mock only external services that can't be containerized
- Never mock the thing you're testing
When mocking is necessary, use github.com/stretchr/testify/mock:
type MockClient struct {
mock.Mock
}
func (m *MockClient) Send(ctx context.Context, msg Message) error {
args := m.Called(ctx, msg)
return args.Error(0)
}
// In test
client := new(MockClient)
client.On("Send", mock.Anything, expectedMsg).Return(nil)
Test Organization
service/
├── domain/
│ ├── account.go
│ └── account_test.go # Unit tests (same package)
├── repository/
│ ├── postgres_repository.go
│ ├── postgres_repository_test.go # Integration tests
│ └── testhelpers/
│ └── container.go # Shared test setup
└── handler/
├── grpc_handler.go
└── grpc_handler_test.go # Handler tests
CI Considerations
- Unit tests run on every commit
- Integration tests (testcontainers) run on PR
- Tests must pass before merge
- Flaky tests must be fixed immediately (not skipped)
Related Documentation
- ADR-0008: Defensive Testing Standards - Testing philosophy
- Testcontainers Usage Guide - Database test setup
- await package - Async polling utility
When not to use it
- →When `time.Sleep` is intentionally used for non-testing purposes
- →When tests share global state or have fixed port bindings
- →When mocking the component currently under test
Limitations
- →The guidelines are specific to Meridian services and Go language
- →It does not cover all possible testing scenarios or languages
- →The skill focuses on testing standards, not on test implementation details for every case
How it compares
This skill enforces specific Go testing patterns and tools like `await` and Testcontainers, providing a standardized and more reliable approach than ad-hoc testing practices.
Compared to similar skills
skill-testing-standards side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| skill-testing-standards (this skill) | 0 | 2mo | No flags | Intermediate |
| webapp-testing | 353 | 3mo | Review | Intermediate |
| ui-ux-expert-skill | 91 | 9mo | Review | Advanced |
| skill-creator | 128 | 3mo | Review | Advanced |
Try saying
Example prompts that trigger this skill in your AI assistant.
You might also like
webapp-testing
anthropics
Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.
ui-ux-expert-skill
fercracix33
Technical workflow for implementing accessible React user interfaces with shadcn/ui, Tailwind CSS, and TanStack Query. Includes 6-phase process with mandatory Style Guide compliance, Context7 best practices consultation, Chrome DevTools validation, and WCAG 2.1 AA accessibility standards. Use after Test Agent, Implementer, and Supabase agents complete their work.
skill-creator
anthropics
Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends Claude's capabilities with specialized knowledge, workflows, or tool integrations.
python-testing-patterns
wshobson
Implement comprehensive testing strategies with pytest, fixtures, mocking, and test-driven development. Use when writing Python tests, setting up test suites, or implementing testing best practices.
dependency-upgrade
wshobson
Manage major dependency version upgrades with compatibility analysis, staged rollout, and comprehensive testing. Use when upgrading framework versions, updating major dependencies, or managing breaking changes in libraries.
playwright-mcp
sfc-gh-dflippo
Browser testing, web scraping, and UI validation using Playwright MCP. Use this skill when you need to test Streamlit apps, validate web interfaces, test responsive design, check accessibility, or automate browser interactions through MCP tools.