-
Notifications
You must be signed in to change notification settings - Fork 256
NO-ISSUE: Add assisted-service-writing-unit-tests agent skill #10123
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
openshift-merge-bot
merged 1 commit into
openshift:master
from
bluesort:add-skill-writing-unit-tests
Apr 10, 2026
+176
−0
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| ../../.cursor/skills/assisted-service-writing-unit-tests/SKILL.md |
169 changes: 169 additions & 0 deletions
169
.cursor/skills/assisted-service-writing-unit-tests/SKILL.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,169 @@ | ||
| --- | ||
| name: assisted-service-writing-unit-tests | ||
| description: Use when writing unit tests in assisted-service with gomock mocks, Ginkgo/Gomega, events, or database. Critical when tests pass but mocks aren't verified, or gomock.NewController appears in code. | ||
| --- | ||
|
|
||
| # 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 | | ||
|
|
||
| ## When to Use Patterns | ||
|
|
||
| ```dot | ||
| 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"]; | ||
| } | ||
| ``` | ||
|
|
||
| ```dot | ||
| 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:** Unit tests in `internal/` and `pkg/` with gomock, Ginkgo/Gomega, database, events. | ||
|
|
||
| **Does NOT cover:** Subsystem tests, E2E, external service integration, performance tests. | ||
|
|
||
| **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 | ||
|
|
||
| ```go | ||
| 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): | ||
| ```go | ||
| BeforeSuite(func() { common.InitializeDBTest() }) | ||
| AfterSuite(func() { common.TerminateDBTest() }) | ||
| ``` | ||
|
|
||
| **Per-test** (isolated DB, tests modify state): | ||
| ```go | ||
| BeforeEach(func() { db, dbName = common.PrepareTestDB() }) | ||
| AfterEach(func() { common.DeleteTestDB(db, dbName) }) | ||
| ``` | ||
|
|
||
| ## Event Testing | ||
|
|
||
| Use `eventstest.NewEventMatcher` with specific matchers: | ||
|
|
||
| ```go | ||
| 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): | ||
| ```go | ||
| 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): | ||
| ```go | ||
| 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()` | ||
|
|
||
| ## 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 | ||
|
|
||
| **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 | | ||
|
|
||
| ## 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()` | ||
| - [ ] Tests pass: `go test -v ./path/to/package` | ||
|
|
||
| **Red flags:** Missing `ctrl.Finish()` → silent failures. Shared variables → flaky tests. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.