mcpbeat

Assisted Service Writing Unit Tests

openshift/assisted-service-writing-unit-tests

Use when writing non-subsystem tests in assisted-service.

2k tokens
context cost
the whole folder, loaded on every use
1
files
instructions only
0
copies elsewhere
how many repositories repackaged it
137
stars on the repo
on the repository, not the skill itself

Install

one command, takes just this skill from the repository
npx skills add https://github.com/openshift/assisted-service --skill assisted-service-writing-unit-tests

The instruction itself

14 sections, as written by the author

Writing Unit Tests in Assisted-Service

Overview

Unit tests in assisted-service use Ginkgo/Gomega BDD framework with gomock for mocking. Core principle: Every gomock controller MUST call ctrl.Finish() in AfterEach to verify mock expectations.

Without ctrl.Finish(), tests pass even when mocks aren't called correctly - silent failures that hide bugs. Tests run against real PostgreSQL (suite or per-test) with automatic cleanup.

Quick Reference

| Scenario | Pattern |

|----------|---------|

| Testing with mocks | Create controller in BeforeEach, call ctrl.Finish() in AfterEach |

| Testing without mocks | Use It() blocks directly or table-driven tests |

| Database per test | PrepareTestDB() in BeforeEach, DeleteTestDB() in AfterEach |

| Database for suite | InitializeDBTest() in BeforeSuite, TerminateDBTest() in AfterSuite |

| Event verification | eventstest.NewEventMatcher with matchers |

| OCP version in tests | Use hardcoded strings or common.TestDefaultConfig — do not use TestVersion() |

When to Use Patterns

digraph mock_decision {
    "Testing interface deps?" [shape=diamond];
    "Need verify calls or control returns?" [shape=diamond];
    "Use gomock mocks" [shape=box];
    "Use real implementation" [shape=box];

    "Testing interface deps?" -> "Need verify calls or control returns?" [label="yes"];
    "Testing interface deps?" -> "Use real implementation" [label="no"];
    "Need verify calls or control returns?" -> "Use gomock mocks" [label="yes"];
    "Need verify calls or control returns?" -> "Use real implementation" [label="no"];
}
digraph db_decision {
    "Tests share data?" [shape=diamond];
    "Tests modify DB state?" [shape=diamond];
    "Suite-level DB" [shape=box];
    "Per-test DB" [shape=box];

    "Tests share data?" -> "Suite-level DB" [label="yes"];
    "Tests share data?" -> "Tests modify DB state?" [label="no"];
    "Tests modify DB state?" -> "Per-test DB" [label="yes"];
    "Tests modify DB state?" -> "Suite-level DB" [label="no"];
}

Scope

Covers: Non-subsystem tests in internal/ and pkg/ with gomock, Ginkgo/Gomega, database, events.

Does NOT cover: Subsystem tests (subsystem/), E2E, external service integration, performance tests. Subsystem tests use different patterns, including common.TestVersion() for OCP versions.

Test files: *_test.go (same package). Suites: *_suite_test.go.

Critical: Gomock Controllers MUST Call Finish()

Every Describe/Context creating a gomock controller needs ctrl.Finish() in AfterEach. No exceptions.

Without ctrl.Finish():

  • .Times(N) - Expected call count NOT checked
  • .MaxTimes(0) - Unexpected calls NOT caught
  • .Do() / .DoAndReturn() - Mock behavior runs without verification
BeforeEach(func() {
    ctrl = gomock.NewController(GinkgoT())
    mockHandler = eventsapi.NewMockHandler(ctrl)
})

AfterEach(func() {
    ctrl.Finish()  // REQUIRED - verifies all EXPECT() constraints
})

Why this matters: Missing ctrl.Finish() causes silent test failures - tests pass even when mocks aren't called correctly. Real bugs found in commit 338133e05 MGMT-23548.

Letter = Spirit: Following the pattern exactly IS following the spirit. This isn't ritual - it's how gomock verification works.

Database Setup Patterns

Suite-level (shared DB, read-only tests):

BeforeSuite(func() { common.InitializeDBTest() })
AfterSuite(func() { common.TerminateDBTest() })

Per-test (isolated DB, tests modify state):

BeforeEach(func() { db, dbName = common.PrepareTestDB() })
AfterEach(func() { common.DeleteTestDB(db, dbName) })

Event Testing

Use eventstest.NewEventMatcher with specific matchers:

mockEvents.EXPECT().SendHostEvent(gomock.Any(), eventstest.NewEventMatcher(
    eventstest.WithNameMatcher(eventgen.HostStatusUpdatedEventName),
    eventstest.WithHostIdMatcher(host.ID.String())))

Matchers: WithNameMatcher, WithHostIdMatcher, WithClusterIdMatcher, WithInfraEnvIdMatcher, WithSeverityMatcher

Table-Driven Tests

DescribeTable (simple cases):

DescribeTable("FunctionName",
    func(input string, valid bool) { /* test logic */ },
    Entry("descriptive case 1", "value1", true),
    Entry("descriptive case 2", "value2", false))

Struct array (complex scenarios):

tests := []struct{name, input string; valid bool}{
    {name: "case 1", input: "val1", valid: true}}
for _, t := range tests { It(t.name, func() { /* ... */ }) }

Test Structure

Ginkgo hierarchy: Describe("Component")Context("when X")It("should Y")

Isolation: No shared state between It blocks. Use BeforeEach for setup.

Gomock matchers: .Times(N), .MaxTimes(0), .AnyTimes(), gomock.Any()

OCP Versions: Use Hardcoded Strings, Not TestVersion()

Do not use common.TestVersion() in non-subsystem tests. TestVersion() resolves versions dynamically from data files and is designed for subsystem tests (subsystem/), which run against the real service. Non-subsystem tests mock version data and should use hardcoded version strings directly.

Correct patterns:

OpenshiftVersion: swag.String("4.14"),

Or use the package-level defaults from internal/common/test_configuration.go:

OpenshiftVersion: swag.String(common.OpenShiftVersion),
ReleaseVersion:   common.ReleaseVersion,
ReleaseImageUrl:  common.ReleaseImageURL,

See docs/dev/test-versions.md for the TestVersion() API used in subsystem tests.

Red Flags - Stop and Fix

These thoughts mean STOP - fix the issue:

  • "Tests are passing, mocks must be working" → Add ctrl.Finish() in AfterEach
  • "Too complex to set up properly" → Follow patterns, don't skip structure
  • "Minimum viable test to satisfy review" → Tests must verify behavior, not theater
  • "I'll add cleanup/verification later" → Do it now, tests will merge broken
  • Code has gomock.NewController but no ctrl.Finish() → Silent failures
  • Using common.TestVersion() outside subsystem/ → Wrong pattern; use hardcoded strings or common.TestDefaultConfig

If you're rationalizing shortcuts due to time pressure, the test will be broken. No exceptions.

Common Mistakes

| Mistake | Symptom | Fix |

|---------|---------|-----|

| Missing ctrl.Finish() | Tests pass when mocks not called | Add ctrl.Finish() in AfterEach |

| Shared variables between tests | Flaky tests, race conditions | Move initialization to BeforeEach |

| Missing GinkgoT() | Controller doesn't report failures | Use gomock.NewController(GinkgoT()) |

| Generic Entry names | "test 1", "test 2" in output | Descriptive: "valid IPv4 CIDR" |

| Wrong DB pattern | Pollution between tests | Suite-level for reads, per-test for writes |

| No event matchers | Generic gomock.Any() for events | Use eventstest.NewEventMatcher with specific matchers |

| Using TestVersion() | Wrong abstraction for non-subsystem tests | Use hardcoded strings or common.TestDefaultConfig |

Before Submitting

Check every test file:

  • [ ] Every gomock.NewController has ctrl.Finish() in AfterEach
  • [ ] No shared state (all setup in BeforeEach)
  • [ ] Descriptive names ("valid IPv4 CIDR" not "test 1")
  • [ ] Database cleanup if using PrepareTestDB()
  • [ ] No TestVersion() usage — use hardcoded strings or common.TestDefaultConfig
  • [ ] Tests pass: go test -v ./path/to/package

Red flags: Missing ctrl.Finish() → silent failures. Shared variables → flaky tests.

How to use it

Copy the folder

Take openshift/assisted-service-writing-unit-tests from the repository into ~/.claude/skills for personal use, or into .claude/skills inside a project.

Check the name does not clash

The agent identifies a skill by the name field in its header. Two skills with the same name cannot sit side by side — one of them will be ignored.