@@ -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
127138expect (event .transaction ).toBe (' GET /users/:id' );
128139expect (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
137160Names 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