Skip to content

Commit 1afb2e6

Browse files
committed
iteration 1 - improve skill
1 parent 5d66c0b commit 1afb2e6

1 file changed

Lines changed: 42 additions & 17 deletions

File tree

.agents/skills/write-tests/SKILL.md

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ Follow these steps in order before writing any test code.
2929
- What cleanup they do in `beforeEach` (`clearAllMocks` vs `restoreAllMocks`)
3030
- How they import the module under test (`../../src/...` vs `@sentry/...`)
3131
- The `describe`/`it` nesting depth and naming style
32+
- What setup functions are called together — does the function under test require companion
33+
initialization? (e.g., does `patchRoute` also need `patchAppUse` to work correctly?)
3234

3335
Match what you find. Consistency within a package matters more than idealized best practice.
3436

@@ -106,32 +108,53 @@ it('sets transaction name from route path', () => {
106108

107109
### Precise assertions
108110

109-
`toMatchObject`, `expect.objectContaining`, and `expect.arrayContaining` silently ignore fields you
110-
_should_ care about. A test that asserts `expect(tags).toMatchObject({})` can never fail — which
111-
means it's not a test. This has caused real bugs to ship in this codebase.
111+
Default to exact matching. `toMatchObject`, `expect.objectContaining`, and `expect.arrayContaining`
112+
silently ignore fields that matter. This has caused real bugs to ship in this codebase.
112113

113-
Choose the right assertion based on what you know about the object:
114-
115-
- **Know all the fields?**`toEqual` (exact match, always preferred)
116-
- **Object too large to enumerate?** → assert individual fields with `.toBe()`
117-
- **Finding an item in an array?**`toContainEqual` + `toHaveLength`
114+
**Use `toEqual` unless you have a specific reason not to.** The same applies to
115+
`toHaveBeenCalledWith` — spell out every argument rather than wrapping in `objectContaining`.
116+
This is the single most common place where loose assertions creep in:
118117

119118
```typescript
120-
// Bad: passes no matter what tags contains
121-
expect(tags).toMatchObject({});
119+
// Bad: silently ignores any missing or extra properties in the call
120+
expect(startSpan).toHaveBeenCalledWith(
121+
expect.objectContaining({ name: 'middleware', op: 'middleware.hono' }),
122+
);
123+
124+
// Good: exact match on the full argument — if the shape changes, the test catches it
125+
expect(startSpan).toHaveBeenCalledWith({
126+
name: 'middleware',
127+
op: 'middleware.hono',
128+
onlyIfParent: true,
129+
parentSpan: fakeRootSpan,
130+
attributes: { 'sentry.op': 'middleware.hono', 'sentry.origin': 'auto.middleware.hono' },
131+
});
132+
```
122133

123-
// Good: exact match on a known, small object
124-
expect(mechanism).toEqual({ handled: false, type: 'auto.http.hono.context_error' });
134+
When you genuinely can't enumerate all fields (e.g., a large framework-generated object), fall
135+
back to individual `.toBe()` checks on the fields that matter:
125136

126-
// Good: individual fields when the full object is too large
137+
```typescript
127138
expect(event.transaction).toBe('GET /users/:id');
128139
expect(event.contexts?.trace?.op).toBe('http.server');
140+
```
129141

130-
// Good: array search paired with length check
131-
expect(spans).toHaveLength(2);
132-
expect(spans).toContainEqual(expect.objectContaining({ description: 'middlewareA' }));
142+
**Every `toContain` / `toContainEqual` needs a `toHaveLength` companion.** Without it, the
143+
assertion passes even if the array has unexpected extra items:
144+
145+
```typescript
146+
// Bad: doesn't notice extra unexpected spans
147+
expect(spanNames).toContain('authMiddleware');
148+
149+
// Good: locks down both content and count
150+
expect(spanNames).toHaveLength(1);
151+
expect(spanNames).toContain('authMiddleware');
133152
```
134153

154+
**Use exported constants, not magic numbers.** If the code under test uses named constants like
155+
`SPAN_STATUS_OK`, reference those same constants in assertions. If the constant's value ever
156+
changes, tests using magic numbers silently pass with wrong expectations.
157+
135158
### Naming
136159

137160
Names should be concise, descriptive, and read as correct English. Lead with the verb.
@@ -372,6 +395,8 @@ Before you're done, verify each test against these criteria:
372395
- [ ] Edge cases covered: empty inputs, boundaries, error paths, null/undefined
373396
- [ ] Realistic test data (not `"foo"`, `"test"`, `123`)
374397
- [ ] No try/catch for error testing — `toThrow` / `rejects.toThrow` only
375-
- [ ] Assertions use `toEqual` by default; `toMatchObject`/`objectContaining` only when justified
398+
- [ ] Assertions use `toEqual` by default; `toHaveBeenCalledWith` spells out full arguments
399+
- [ ] Array lookups (`toContain`, `toContainEqual`) paired with `toHaveLength`
400+
- [ ] Uses exported constants (e.g., `SPAN_STATUS_OK`) instead of magic numbers
376401
- [ ] Passes in isolation (`vitest run <file>` or single Playwright test)
377402
- [ ] Matches the existing conventions of the package's test directory

0 commit comments

Comments
 (0)