Skip to content

Instantly share code, notes, and snippets.

@samwightt
Last active September 8, 2025 18:42
Show Gist options
  • Save samwightt/fb0acb98374bf70105f34a7e398d5923 to your computer and use it in GitHub Desktop.
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.
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