|
| 1 | +# Require `toHaveBeenCalledTimes()` when using `toHaveBeenCalledWith()` (`pair-to-have-been-called-assertions`) |
| 2 | + |
| 3 | +🔧 This rule is automatically fixable by the |
| 4 | +[`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). |
| 5 | + |
| 6 | +<!-- end auto-generated rule header --> |
| 7 | + |
| 8 | +## Rule Details |
| 9 | + |
| 10 | +When testing mock functions, developers often use `toHaveBeenCalledWith()`, |
| 11 | +`toBeCalledWith()`, `toHaveBeenNthCalledWith()`, `toBeNthCalledWith()`, |
| 12 | +`toHaveBeenLastCalledWith()`, or `toBeLastCalledWith()` to verify that a |
| 13 | +function was called with specific arguments. However, without also checking the |
| 14 | +call count using `toHaveBeenCalledTimes()` or `toBeCalledTimes()`, the test can |
| 15 | +pass even if the mock was called more times than expected, potentially masking |
| 16 | +bugs. |
| 17 | + |
| 18 | +This rule requires that whenever you use these matchers with arguments, you must |
| 19 | +also use the corresponding `toHaveBeenCalledTimes()` or `toBeCalledTimes()` |
| 20 | +matcher for the same mock function to ensure an exact call count. |
| 21 | + |
| 22 | +### Benefits |
| 23 | + |
| 24 | +- **Prevents false positives**: Ensures tests fail when a mock is called more |
| 25 | + times than expected |
| 26 | +- **Makes test intentions explicit**: Clearly documents how many times a |
| 27 | + function should be called |
| 28 | +- **Improves test reliability**: Catches unexpected behavior where functions are |
| 29 | + called multiple times |
| 30 | +- **Follows testing best practices**: Encourages complete and precise assertions |
| 31 | + |
| 32 | +## Examples |
| 33 | + |
| 34 | +### Incorrect |
| 35 | + |
| 36 | +```js |
| 37 | +expect(mockFn).toHaveBeenCalledWith('arg'); |
| 38 | + |
| 39 | +expect(mockFn).toBeCalledWith('arg'); |
| 40 | + |
| 41 | +// Multiple assertions without call count check |
| 42 | +expect(mockFn).toHaveBeenCalledWith('arg1'); |
| 43 | +expect(mockFn).toHaveBeenCalledWith('arg2'); |
| 44 | + |
| 45 | +// Using toHaveBeenNthCalledWith without call count |
| 46 | +expect(mockFn).toHaveBeenNthCalledWith(1, 'first'); |
| 47 | + |
| 48 | +// Using toHaveBeenLastCalledWith without call count |
| 49 | +expect(mockFn).toHaveBeenLastCalledWith('last'); |
| 50 | + |
| 51 | +// Using toBeNthCalledWith without call count |
| 52 | +expect(mockFn).toBeNthCalledWith(2, 'second'); |
| 53 | + |
| 54 | +// Using toBeLastCalledWith without call count |
| 55 | +expect(mockFn).toBeLastCalledWith('last'); |
| 56 | +``` |
| 57 | + |
| 58 | +### Correct |
| 59 | + |
| 60 | +```js |
| 61 | +expect(mockFn).toHaveBeenCalledTimes(1); |
| 62 | +expect(mockFn).toHaveBeenCalledWith('arg'); |
| 63 | + |
| 64 | +expect(mockFn).toBeCalledTimes(1); |
| 65 | +expect(mockFn).toBeCalledWith('arg'); |
| 66 | + |
| 67 | +// Multiple mocks, each with call count |
| 68 | +expect(mockFn1).toHaveBeenCalledTimes(1); |
| 69 | +expect(mockFn1).toHaveBeenCalledWith('arg1'); |
| 70 | +expect(mockFn2).toHaveBeenCalledTimes(1); |
| 71 | +expect(mockFn2).toHaveBeenCalledWith('arg2'); |
| 72 | + |
| 73 | +// Using toHaveBeenNthCalledWith with call count |
| 74 | +expect(mockFn).toHaveBeenCalledTimes(2); |
| 75 | +expect(mockFn).toHaveBeenNthCalledWith(1, 'first'); |
| 76 | + |
| 77 | +// Using toHaveBeenLastCalledWith with call count |
| 78 | +expect(mockFn).toHaveBeenCalledTimes(3); |
| 79 | +expect(mockFn).toHaveBeenLastCalledWith('last'); |
| 80 | + |
| 81 | +// Using toBeNthCalledWith with call count |
| 82 | +expect(mockFn).toBeCalledTimes(2); |
| 83 | +expect(mockFn).toBeNthCalledWith(2, 'second'); |
| 84 | + |
| 85 | +// Using toBeLastCalledWith with call count |
| 86 | +expect(mockFn).toBeCalledTimes(1); |
| 87 | +expect(mockFn).toBeLastCalledWith('only'); |
| 88 | + |
| 89 | +// Mixed matchers with call count |
| 90 | +expect(mockFn).toHaveBeenCalledTimes(3); |
| 91 | +expect(mockFn).toHaveBeenCalledWith('arg1'); |
| 92 | +expect(mockFn).toHaveBeenNthCalledWith(2, 'arg2'); |
| 93 | +expect(mockFn).toHaveBeenLastCalledWith('arg3'); |
| 94 | + |
| 95 | +// Empty call (no arguments) doesn't require call count |
| 96 | +expect(mockFn).toHaveBeenCalledWith(); |
| 97 | + |
| 98 | +// Using 'not' modifier doesn't require call count |
| 99 | +expect(mockFn).not.toHaveBeenCalledWith('arg'); |
| 100 | + |
| 101 | +// Only checking call count is fine |
| 102 | +expect(mockFn).toHaveBeenCalledTimes(1); |
| 103 | +``` |
| 104 | + |
| 105 | +## Additional Checks |
| 106 | + |
| 107 | +### Contradictory Assertions |
| 108 | + |
| 109 | +This rule also detects contradictory assertions where `toHaveBeenCalledTimes(0)` |
| 110 | +is used together with `toHaveBeenCalledWith()`. Since `toHaveBeenCalledWith()` |
| 111 | +expects the mock to be called at least once, using it with |
| 112 | +`toHaveBeenCalledTimes(0)` is contradictory. |
| 113 | + |
| 114 | +```js |
| 115 | +// ❌ Incorrect - contradictory assertions |
| 116 | +test('foo', () => { |
| 117 | + expect(mockFn).toHaveBeenCalledTimes(0); |
| 118 | + expect(mockFn).toHaveBeenCalledWith('arg'); // This expects a call! |
| 119 | +}); |
| 120 | + |
| 121 | +// ✅ Correct - consistent assertions |
| 122 | +test('foo', () => { |
| 123 | + expect(mockFn).toHaveBeenCalledTimes(0); |
| 124 | + // No CalledWith assertions |
| 125 | +}); |
| 126 | + |
| 127 | +// ✅ Correct - consistent assertions |
| 128 | +test('foo', () => { |
| 129 | + expect(mockFn).toHaveBeenCalledTimes(1); |
| 130 | + expect(mockFn).toHaveBeenCalledWith('arg'); |
| 131 | +}); |
| 132 | +``` |
| 133 | + |
| 134 | +## When Not To Use It |
| 135 | + |
| 136 | +If you have a specific testing strategy where checking call counts is not |
| 137 | +necessary or you're only interested in verifying that a function was called with |
| 138 | +specific arguments at least once, you can disable this rule. |
| 139 | + |
| 140 | +However, it's generally recommended to keep this rule enabled as it helps catch |
| 141 | +bugs where functions are called more times than expected, leading to more robust |
| 142 | +and reliable tests. |
0 commit comments