eslint-plugin-jest 21.26.1 → 21.27.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/docs/rules/prefer-spy-on.md +41 -0
- package/index.js +2 -0
- package/package.json +1 -1
- package/rules/__tests__/no-focused-tests.test.js +4 -0
- package/rules/__tests__/prefer-spy-on.test.js +109 -0
- package/rules/__tests__/valid-expect-in-promise.test.js +9 -0
- package/rules/no-focused-tests.js +11 -0
- package/rules/prefer-spy-on.js +70 -0
- package/rules/util.js +3 -0
- package/rules/valid-expect-in-promise.js +1 -0
package/README.md
CHANGED
|
@@ -83,7 +83,7 @@ for more information about extending configuration files.
|
|
|
83
83
|
| [consistent-test-it][] | Enforce consistent test or it keyword | | ![fixable-green][] |
|
|
84
84
|
| [expect-expect][] | Enforce assertion to be made in a test body | | |
|
|
85
85
|
| [lowercase-name][] | Disallow capitalized test names | | ![fixable-green][] |
|
|
86
|
-
| [no-alias-methods][] | Disallow alias methods | |
|
|
86
|
+
| [no-alias-methods][] | Disallow alias methods | | ![fixable-green][] |
|
|
87
87
|
| [no-disabled-tests][] | Disallow disabled tests | ![recommended][] | |
|
|
88
88
|
| [no-focused-tests][] | Disallow focused tests | ![recommended][] | |
|
|
89
89
|
| [no-hooks][] | Disallow setup and teardown hooks | | |
|
|
@@ -95,6 +95,7 @@ for more information about extending configuration files.
|
|
|
95
95
|
| [no-test-prefixes][] | Disallow using `f` & `x` prefixes to define focused/skipped tests | | ![fixable-green][] |
|
|
96
96
|
| [no-test-return-statement][] | Disallow explicitly returning from tests | | |
|
|
97
97
|
| [prefer-expect-assertions][] | Suggest using `expect.assertions()` OR `expect.hasAssertions()` | | |
|
|
98
|
+
| [prefer-spy-on][] | Suggest using `jest.spyOn()` | | ![fixable-green][] |
|
|
98
99
|
| [prefer-strict-equal][] | Suggest using `toStrictEqual()` | | ![fixable-green][] |
|
|
99
100
|
| [prefer-to-be-null][] | Suggest using `toBeNull()` | | ![fixable-green][] |
|
|
100
101
|
| [prefer-to-be-undefined][] | Suggest using `toBeUndefined()` | | ![fixable-green][] |
|
|
@@ -126,6 +127,7 @@ for more information about extending configuration files.
|
|
|
126
127
|
[no-test-prefixes]: docs/rules/no-test-prefixes.md
|
|
127
128
|
[no-test-return-statement]: docs/rules/no-test-return-statement.md
|
|
128
129
|
[prefer-expect-assertions]: docs/rules/prefer-expect-assertions.md
|
|
130
|
+
[prefer-spy-on]: docs/rules/prefer-spy-on.md
|
|
129
131
|
[prefer-strict-equal]: docs/rules/prefer-strict-equal.md
|
|
130
132
|
[prefer-to-be-null]: docs/rules/prefer-to-be-null.md
|
|
131
133
|
[prefer-to-be-undefined]: docs/rules/prefer-to-be-undefined.md
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Suggest using `jest.spyOn()` (prefer-spy-on)
|
|
2
|
+
|
|
3
|
+
When mocking a function by overwriting a property you have to manually restore
|
|
4
|
+
the original implementation when cleaning up. When using `jest.spyOn()` Jest
|
|
5
|
+
keeps track of changes, and they can be restored with `jest.restoreAllMocks()`,
|
|
6
|
+
`mockFn.mockRestore()` or by setting `restoreMocks` to `true` in the Jest
|
|
7
|
+
config.
|
|
8
|
+
|
|
9
|
+
Note: The mock created by `jest.spyOn()` still behaves the same as the original
|
|
10
|
+
function. The original function can be overwritten with
|
|
11
|
+
`mockFn.mockImplementation()` or by some of the
|
|
12
|
+
[other mock functions](https://jestjs.io/docs/en/mock-function-api).
|
|
13
|
+
|
|
14
|
+
```js
|
|
15
|
+
Date.now = jest.fn(); // Original behaviour lost, returns undefined
|
|
16
|
+
|
|
17
|
+
jest.spyOn(Date, 'now'); // Turned into a mock function but behaviour hasn't changed
|
|
18
|
+
jest.spyOn(Date, 'now').mockImplementation(() => 10); // Will always return 10
|
|
19
|
+
jest.spyOn(Date, 'now').mockReturnValue(10); // Will always return 10
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Rule details
|
|
23
|
+
|
|
24
|
+
This rule triggers a warning if an object's property is overwritten with a jest
|
|
25
|
+
mock.
|
|
26
|
+
|
|
27
|
+
### Default configuration
|
|
28
|
+
|
|
29
|
+
The following patterns are considered warnings:
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
Date.now = jest.fn();
|
|
33
|
+
Date.now = jest.fn(() => 10);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
These patterns would not be considered warnings:
|
|
37
|
+
|
|
38
|
+
```js
|
|
39
|
+
jest.spyOn(Date, 'now');
|
|
40
|
+
jest.spyOn(Date, 'now').mockImplementation(() => 10);
|
|
41
|
+
```
|
package/index.js
CHANGED
|
@@ -12,6 +12,7 @@ const noJestImport = require('./rules/no-jest-import');
|
|
|
12
12
|
const noLargeSnapshots = require('./rules/no-large-snapshots');
|
|
13
13
|
const noTestPrefixes = require('./rules/no-test-prefixes');
|
|
14
14
|
const noTestReturnStatement = require('./rules/no-test-return-statement');
|
|
15
|
+
const preferSpyOn = require('./rules/prefer-spy-on');
|
|
15
16
|
const preferToBeNull = require('./rules/prefer-to-be-null');
|
|
16
17
|
const preferToBeUndefined = require('./rules/prefer-to-be-undefined');
|
|
17
18
|
const preferToContain = require('./rules/prefer-to-contain');
|
|
@@ -84,6 +85,7 @@ module.exports = {
|
|
|
84
85
|
'no-large-snapshots': noLargeSnapshots,
|
|
85
86
|
'no-test-prefixes': noTestPrefixes,
|
|
86
87
|
'no-test-return-statement': noTestReturnStatement,
|
|
88
|
+
'prefer-spy-on': preferSpyOn,
|
|
87
89
|
'prefer-to-be-null': preferToBeNull,
|
|
88
90
|
'prefer-to-be-undefined': preferToBeUndefined,
|
|
89
91
|
'prefer-to-contain': preferToContain,
|
package/package.json
CHANGED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const RuleTester = require('eslint').RuleTester;
|
|
4
|
+
const rule = require('../prefer-spy-on');
|
|
5
|
+
|
|
6
|
+
const ruleTester = new RuleTester({
|
|
7
|
+
parserOptions: {
|
|
8
|
+
ecmaVersion: 6,
|
|
9
|
+
},
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
ruleTester.run('prefer-spy-on', rule, {
|
|
13
|
+
valid: [
|
|
14
|
+
'Date.now = () => 10',
|
|
15
|
+
'window.fetch = jest.fn',
|
|
16
|
+
'Date.now = fn()',
|
|
17
|
+
'obj.mock = jest.something()',
|
|
18
|
+
'const mock = jest.fn()',
|
|
19
|
+
'mock = jest.fn()',
|
|
20
|
+
'const mockObj = { mock: jest.fn() }',
|
|
21
|
+
'mockObj = { mock: jest.fn() }',
|
|
22
|
+
'window[`${name}`] = jest[`fn${expression}`]()',
|
|
23
|
+
],
|
|
24
|
+
invalid: [
|
|
25
|
+
{
|
|
26
|
+
code: 'obj.a = jest.fn(); const test = 10;',
|
|
27
|
+
errors: [
|
|
28
|
+
{
|
|
29
|
+
message: 'Use jest.spyOn() instead.',
|
|
30
|
+
type: 'AssignmentExpression',
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
output: "jest.spyOn(obj, 'a'); const test = 10;",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
code: "Date['now'] = jest['fn']()",
|
|
37
|
+
errors: [
|
|
38
|
+
{
|
|
39
|
+
message: 'Use jest.spyOn() instead.',
|
|
40
|
+
type: 'AssignmentExpression',
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
output: "jest.spyOn(Date, 'now')",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
code: 'window[`${name}`] = jest[`fn`]()',
|
|
47
|
+
errors: [
|
|
48
|
+
{
|
|
49
|
+
message: 'Use jest.spyOn() instead.',
|
|
50
|
+
type: 'AssignmentExpression',
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
output: 'jest.spyOn(window, `${name}`)',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
code: "obj['prop' + 1] = jest['fn']()",
|
|
57
|
+
errors: [
|
|
58
|
+
{
|
|
59
|
+
message: 'Use jest.spyOn() instead.',
|
|
60
|
+
type: 'AssignmentExpression',
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
output: "jest.spyOn(obj, 'prop' + 1)",
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
code: 'obj.one.two = jest.fn(); const test = 10;',
|
|
67
|
+
errors: [
|
|
68
|
+
{
|
|
69
|
+
message: 'Use jest.spyOn() instead.',
|
|
70
|
+
type: 'AssignmentExpression',
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
output: "jest.spyOn(obj.one, 'two'); const test = 10;",
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
code: 'obj.a = jest.fn(() => 10)',
|
|
77
|
+
errors: [
|
|
78
|
+
{
|
|
79
|
+
message: 'Use jest.spyOn() instead.',
|
|
80
|
+
type: 'AssignmentExpression',
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
output: "jest.spyOn(obj, 'a').mockImplementation(() => 10)",
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
code:
|
|
87
|
+
"obj.a.b = jest.fn(() => ({})).mockReturnValue('default').mockReturnValueOnce('first call'); test();",
|
|
88
|
+
errors: [
|
|
89
|
+
{
|
|
90
|
+
message: 'Use jest.spyOn() instead.',
|
|
91
|
+
type: 'AssignmentExpression',
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
output:
|
|
95
|
+
"jest.spyOn(obj.a, 'b').mockImplementation(() => ({})).mockReturnValue('default').mockReturnValueOnce('first call'); test();",
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
code: 'window.fetch = jest.fn(() => ({})).one.two().three().four',
|
|
99
|
+
errors: [
|
|
100
|
+
{
|
|
101
|
+
message: 'Use jest.spyOn() instead.',
|
|
102
|
+
type: 'AssignmentExpression',
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
output:
|
|
106
|
+
"jest.spyOn(window, 'fetch').mockImplementation(() => ({})).one.two().three().four",
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
});
|
|
@@ -182,6 +182,15 @@ ruleTester.run('valid-expect-in-promise', rule, {
|
|
|
182
182
|
],
|
|
183
183
|
|
|
184
184
|
valid: [
|
|
185
|
+
`
|
|
186
|
+
it('it1', () => new Promise((done) => {
|
|
187
|
+
test()
|
|
188
|
+
.then(() => {
|
|
189
|
+
expect(someThing).toEqual(true);
|
|
190
|
+
done();
|
|
191
|
+
});
|
|
192
|
+
}));
|
|
193
|
+
`,
|
|
185
194
|
`
|
|
186
195
|
it('it1', () => {
|
|
187
196
|
return somePromise.then(() => {
|
|
@@ -30,6 +30,17 @@ module.exports = {
|
|
|
30
30
|
const callee = node.callee;
|
|
31
31
|
|
|
32
32
|
if (callee.type === 'MemberExpression') {
|
|
33
|
+
if (
|
|
34
|
+
callee.object.type === 'Identifier' &&
|
|
35
|
+
isCallToFocusedTestFunction(callee.object)
|
|
36
|
+
) {
|
|
37
|
+
context.report({
|
|
38
|
+
message: 'Unexpected focused test.',
|
|
39
|
+
node: callee.object,
|
|
40
|
+
});
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
33
44
|
if (
|
|
34
45
|
callee.object.type === 'MemberExpression' &&
|
|
35
46
|
isCallToTestOnlyFunction(callee.object)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const getDocsUrl = require('./util').getDocsUrl;
|
|
4
|
+
const getNodeName = require('./util').getNodeName;
|
|
5
|
+
|
|
6
|
+
const getJestFnCall = node => {
|
|
7
|
+
if (
|
|
8
|
+
(node.type !== 'CallExpression' && node.type !== 'MemberExpression') ||
|
|
9
|
+
(node.callee && node.callee.type !== 'MemberExpression')
|
|
10
|
+
) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const obj = node.callee ? node.callee.object : node.object;
|
|
15
|
+
|
|
16
|
+
if (obj.type === 'Identifier') {
|
|
17
|
+
return node.type === 'CallExpression' &&
|
|
18
|
+
getNodeName(node.callee) === 'jest.fn'
|
|
19
|
+
? node
|
|
20
|
+
: null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return getJestFnCall(obj);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
module.exports = {
|
|
27
|
+
meta: {
|
|
28
|
+
docs: {
|
|
29
|
+
url: getDocsUrl(__filename),
|
|
30
|
+
},
|
|
31
|
+
fixable: 'code',
|
|
32
|
+
},
|
|
33
|
+
create(context) {
|
|
34
|
+
return {
|
|
35
|
+
AssignmentExpression(node) {
|
|
36
|
+
if (node.left.type !== 'MemberExpression') return;
|
|
37
|
+
|
|
38
|
+
const jestFnCall = getJestFnCall(node.right);
|
|
39
|
+
|
|
40
|
+
if (!jestFnCall) return;
|
|
41
|
+
|
|
42
|
+
context.report({
|
|
43
|
+
node,
|
|
44
|
+
message: 'Use jest.spyOn() instead.',
|
|
45
|
+
fix(fixer) {
|
|
46
|
+
const leftPropQuote =
|
|
47
|
+
node.left.property.type === 'Identifier' ? "'" : '';
|
|
48
|
+
const arg = jestFnCall.arguments[0];
|
|
49
|
+
const argSource = arg && context.getSourceCode().getText(arg);
|
|
50
|
+
const mockImplementation = argSource
|
|
51
|
+
? `.mockImplementation(${argSource})`
|
|
52
|
+
: '';
|
|
53
|
+
|
|
54
|
+
return [
|
|
55
|
+
fixer.insertTextBefore(node.left, `jest.spyOn(`),
|
|
56
|
+
fixer.replaceTextRange(
|
|
57
|
+
[node.left.object.end, node.left.property.start],
|
|
58
|
+
`, ${leftPropQuote}`
|
|
59
|
+
),
|
|
60
|
+
fixer.replaceTextRange(
|
|
61
|
+
[node.left.property.end, jestFnCall.end],
|
|
62
|
+
`${leftPropQuote})${mockImplementation}`
|
|
63
|
+
),
|
|
64
|
+
];
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
},
|
|
70
|
+
};
|
package/rules/util.js
CHANGED
|
@@ -107,6 +107,9 @@ const getNodeName = node => {
|
|
|
107
107
|
return node.name;
|
|
108
108
|
case 'Literal':
|
|
109
109
|
return node.value;
|
|
110
|
+
case 'TemplateLiteral':
|
|
111
|
+
if (node.expressions.length === 0) return node.quasis[0].value.cooked;
|
|
112
|
+
break;
|
|
110
113
|
case 'MemberExpression':
|
|
111
114
|
return joinNames(getNodeName(node.object), getNodeName(node.property));
|
|
112
115
|
}
|
|
@@ -88,6 +88,7 @@ const getTestFunction = node => {
|
|
|
88
88
|
const isParentThenOrPromiseReturned = (node, testFunctionBody) => {
|
|
89
89
|
return (
|
|
90
90
|
testFunctionBody.type === 'CallExpression' ||
|
|
91
|
+
testFunctionBody.type === 'NewExpression' ||
|
|
91
92
|
node.parent.parent.type === 'ReturnStatement' ||
|
|
92
93
|
isPromiseReturnedLater(node, testFunctionBody) ||
|
|
93
94
|
isThenOrCatch(node.parent.parent)
|