Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add no-mocks-import rule #246

Merged
merged 1 commit into from Apr 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Expand Up @@ -102,6 +102,7 @@ for more information about extending configuration files.
| [no-identical-title][] | Disallow identical titles | ![recommended][] | |
| [no-jasmine-globals][] | Disallow Jasmine globals | ![recommended][] | ![fixable-yellow][] |
| [no-jest-import][] | Disallow importing `jest` | ![recommended][] | |
| [no-mocks-import][] | Disallow manually importing from `__mocks__` | | |
| [no-large-snapshots][] | Disallow large snapshots | | |
| [no-test-callback][] | Using a callback in asynchronous tests | | ![fixable-green][] |
| [no-test-prefixes][] | Disallow using `f` & `x` prefixes to define focused/skipped tests | ![recommended][] | ![fixable-green][] |
Expand Down Expand Up @@ -138,6 +139,7 @@ for more information about extending configuration files.
[no-identical-title]: docs/rules/no-identical-title.md
[no-jasmine-globals]: docs/rules/no-jasmine-globals.md
[no-jest-import]: docs/rules/no-jest-import.md
[no-mocks-import]: docs/rules/no-mocks-import.md
[no-large-snapshots]: docs/rules/no-large-snapshots.md
[no-test-callback]: docs/rules/no-test-callback.md
[no-test-prefixes]: docs/rules/no-test-prefixes.md
Expand Down
2 changes: 1 addition & 1 deletion __tests__/rules.test.js
Expand Up @@ -5,7 +5,7 @@ const path = require('path');
const { rules } = require('../index');

const ruleNames = Object.keys(rules);
const numberOfRules = 30;
const numberOfRules = 31;

describe('rules', () => {
it('should have a corresponding doc for each rule', () => {
Expand Down
27 changes: 27 additions & 0 deletions docs/rules/no-mocks-import.md
@@ -0,0 +1,27 @@
# Disallow manually importing from `__mocks__` (no-mocks-import)

When using `jest.mock`, your tests (just like the code being tested) should
import from `./x`, not `./__mocks__/x`. Not following this rule can lead to
confusion, because you will have multiple instances of the mocked module:

```js
jest.mock('./x');
const x1 = require('./x');
const x2 = require('./__mocks__/x');

test('x', () => {
expect(x1).toBe(x2); // fails! They are both instances of `./__mocks__/x`, but not referentially equal
});
```

### Rule details

This rule reports imports from a path containing a `__mocks__` component.

Example violations:

```js
import thing from './__mocks__/index';
require('./__mocks__/index');
require('__mocks__');
```
1 change: 1 addition & 0 deletions index.js
Expand Up @@ -27,6 +27,7 @@ module.exports = {
'jest/no-focused-tests': 'error',
'jest/no-identical-title': 'error',
'jest/no-jest-import': 'error',
// 'jest/no-mocks-import': 'error',
'jest/no-jasmine-globals': 'warn',
'jest/no-test-prefixes': 'error',
'jest/valid-describe': 'error',
Expand Down
96 changes: 96 additions & 0 deletions rules/__tests__/no-mocks-import.test.js
@@ -0,0 +1,96 @@
'use strict';

const rule = require('../no-mocks-import.js');
const { RuleTester } = require('eslint');
const ruleTester = new RuleTester();
const message = `Mocks should not be manually imported from a __mocks__ directory. Instead use jest.mock and import from the original module path.`;

ruleTester.run('no-mocks-import', rule, {
valid: [
{
code: 'import something from "something"',
parserOptions: { sourceType: 'module' },
},
'require("somethingElse")',
'require("./__mocks__.js")',
'require("./__mocks__x")',
'require("./__mocks__x/x")',
'require("./x__mocks__")',
'require("./x__mocks__/x")',
'require()',
'entirelyDifferent(fn)',
],
invalid: [
{
code: 'require("./__mocks__")',
errors: [
{
endColumn: 22,
column: 9,
message,
},
],
},
{
code: 'require("./__mocks__/")',
errors: [
{
endColumn: 23,
column: 9,
message,
},
],
},
{
code: 'require("./__mocks__/index")',
errors: [
{
endColumn: 28,
column: 9,
message,
},
],
},
{
code: 'require("__mocks__")',
errors: [
{
endColumn: 20,
column: 9,
message,
},
],
},
{
code: 'require("__mocks__/")',
errors: [
{
endColumn: 21,
column: 9,
message,
},
],
},
{
code: 'require("__mocks__/index")',
errors: [
{
endColumn: 26,
column: 9,
message,
},
],
},
{
code: 'import thing from "./__mocks__/index"',
parserOptions: { sourceType: 'module' },
errors: [
{
endColumn: 38,
column: 1,
message,
},
],
},
],
});
34 changes: 34 additions & 0 deletions rules/no-mocks-import.js
@@ -0,0 +1,34 @@
'use strict';

const { posix } = require('path');
const { getDocsUrl } = require('./util');

const mocksDirName = '__mocks__';
const message = `Mocks should not be manually imported from a ${mocksDirName} directory. Instead use jest.mock and import from the original module path.`;

const isMockPath = path => path.split(posix.sep).includes(mocksDirName);

module.exports = {
meta: {
docs: {
url: getDocsUrl(__filename),
},
},
create(context) {
return {
ImportDeclaration(node) {
if (isMockPath(node.source.value)) {
context.report({ node, message });
}
},
'CallExpression[callee.name="require"]'(node) {
if (node.arguments.length && isMockPath(node.arguments[0].value)) {
context.report({
loc: node.arguments[0].loc,
message,
});
}
},
};
},
};