SK

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.zip

Installs 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 services
58 charsno explicit “when” trigger
Intermediate

Key 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

You give it
Go test code with asynchronous operations or database interactions
You get back
reliable, isolated, and fast-running tests that adhere to Meridian standards

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

  1. No time.Sleep - Use the await package for async assertions
  2. Defensive testing - Test happy paths AND unhappy paths (see ADR-0008)
  3. Table-driven tests - Use Go table-driven patterns with rationale
  4. Isolation - Tests must not depend on each other
  5. 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.As work)

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:

  1. Use testcontainers for database tests (real PostgreSQL)
  2. Use in-memory implementations for simple interfaces
  3. Mock only external services that can't be containerized
  4. 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

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.

SkillInstallsUpdatedSafetyDifficulty
skill-testing-standards (this skill)02moNo flagsIntermediate
webapp-testing3533moReviewIntermediate
ui-ux-expert-skill919moReviewAdvanced
skill-creator1283moReviewAdvanced

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.

353585

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.

91244

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.

128200

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.

77204

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.

26240

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.

33197

Search skills

Search the agent skills registry