Last active
September 8, 2025 18:42
-
-
Save samwightt/fb0acb98374bf70105f34a7e398d5923 to your computer and use it in GitHub Desktop.
ast-grep rule for when you're missing a `done` callback in an observable's `subscribe`. Only triggers when there are assertions inside the subscribe as we don't want to be too hasty.
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
language: TypeScript | |
id: missing-done-callback-in-subscribe | |
message: When subscribing to an observable inside a test function, you must call the 'done' function. Otherwise the assertions will never run. | |
severity: error | |
note: | | |
When testing observables in Jest, you must call the `done()` function in the subscribe callback. Jest provides a `done` function to test functions that must be executed to let Jest know when the test is done. | |
If you don't call the done function, the assertions will never run because Jest will finish the test before the subscribe callback is called. | |
If possible, it is encouraged to use `firstValueFrom` or `lastValueFrom` instead of subscribing to an observable directly in a test function. These handle subscribing / unsubscribing for you. | |
Incorrect code: | |
```ts | |
it('works', () => { | |
observable.subscribe((someData) => { | |
expect(someData).toBeTruthy() // Will not run because no done function is called! | |
}) | |
}) | |
``` | |
Correct code: | |
```ts | |
it('works', (done) => { | |
observable.subscribe((someData) => { | |
expect(someData).toBeTruthy() | |
done() // Calling done after finished makes sure Jest waits for the assertions to run. | |
}) | |
}) | |
``` | |
rule: | |
any: | |
- matches: subscription | |
inside: | |
pattern: '$_.subscribe($$$_)' | |
all: | |
- pattern: $SUBSCRIPTION | |
stopBy: end | |
inside: | |
pattern: 'it($$$_)' | |
stopBy: end | |
has: | |
kind: arguments | |
has: | |
nthChild: 2 | |
any: | |
- pattern: 'function() { $$$_ }' | |
- pattern: '() => { $$$_ }' | |
- matches: subscription | |
not: | |
has: | |
pattern: $DONE_FUNC() | |
stopBy: end | |
inside: | |
pattern: '$_.subscribe($$$_)' | |
all: | |
- pattern: $SUBSCRIPTION | |
stopBy: end | |
inside: | |
pattern: 'it($$$_)' | |
stopBy: end | |
has: | |
kind: arguments | |
has: | |
nthChild: 2 | |
any: | |
- pattern: 'function($DONE_FUNC) { $$$_ }' | |
- pattern: ($DONE_FUNC) => { $$$_ } | |
- pattern: $DONE_FUNC => { $$$_ } | |
labels: | |
DONE_FUNC: | |
message: The done callback is passed but is never called. | |
style: primary | |
SUBSCRIPTION: | |
message: This subscription is missing a done callback. | |
style: primary | |
ASSERTION: | |
message: This assertion will never run because there is no done callback. | |
style: secondary | |
files: | |
- '**/*.spec.ts' | |
- '**/*.test.ts' | |
utils: | |
subscription: | |
any: | |
- kind: arrow_function | |
- kind: function_expression | |
has: | |
pattern: 'expect($$$_)' | |
all: | |
- pattern: $ASSERTION | |
field: body | |
stopBy: end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment