Skip to content

Commit 498294d

Browse files
authored
docs(no-conditional-expect): add section on how to catch errors for testing (#867)
* docs(no-conditional-expect): add section about how to catch errors * docs(no-conditional-expect): reword initial paragraph of rule details
1 parent 472e6ac commit 498294d

File tree

1 file changed

+57
-3
lines changed

1 file changed

+57
-3
lines changed

docs/rules/no-conditional-expect.md

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ assumed to be promises.
88

99
## Rule Details
1010

11-
Jest considered a test to have failed if it throws an error, rather than on if
12-
any particular function is called, meaning conditional calls to `expect` could
13-
result in tests silently being skipped.
11+
Jest only considers a test to have failed if it throws an error, meaning if
12+
calls to assertion functions like `expect` occur in conditional code such as a
13+
`catch` statement, tests can end up passing but not actually test anything.
1414

1515
Additionally, conditionals tend to make tests more brittle and complex, as they
1616
increase the amount of mental thinking needed to understand what is actually
@@ -79,3 +79,57 @@ it('throws an error', async () => {
7979
await expect(foo).rejects.toThrow(Error);
8080
});
8181
```
82+
83+
### How to catch a thrown error for testing without violating this rule
84+
85+
A common situation that comes up with this rule is when wanting to test
86+
properties on a thrown error, as Jest's `toThrow` matcher only checks the
87+
`message` property.
88+
89+
Most people write something like this:
90+
91+
```typescript
92+
describe('when the http request fails', () => {
93+
it('includes the status code in the error', async () => {
94+
try {
95+
await makeRequest(url);
96+
} catch (error) {
97+
expect(error).toHaveProperty('statusCode', 404);
98+
}
99+
});
100+
});
101+
```
102+
103+
As stated above, the problem with this is that if `makeRequest()` doesn't throw
104+
the test will still pass as if the `expect` had been called.
105+
106+
While you can use `expect.assertions` & `expect.hasAssertions` for these
107+
situations, they only work with `expect`.
108+
109+
A better way to handle this situation is to introduce a wrapper to handle the
110+
catching, and otherwise returns a specific "no error thrown" error if nothing is
111+
thrown by the wrapped function:
112+
113+
```typescript
114+
class NoErrorThrownError extends Error {}
115+
116+
const getError = async <TError>(call: () => unknown): Promise<TError> => {
117+
try {
118+
await call();
119+
120+
throw new NoErrorThrownError();
121+
} catch (error: unknown) {
122+
return error as TError;
123+
}
124+
};
125+
126+
describe('when the http request fails', () => {
127+
it('includes the status code in the error', async () => {
128+
const error = await getError(async () => makeRequest(url));
129+
130+
// check that the returned error wasn't that no error was thrown
131+
expect(error).not.toBeInstanceOf(NoErrorThrownError);
132+
expect(error).toHaveProperty('statusCode', 404);
133+
});
134+
});
135+
```

0 commit comments

Comments
 (0)