Skip to content

Commit

Permalink
Add no-before-after rule
Browse files Browse the repository at this point in the history
`before()` and `after()` run once before and after all examples. In
practice, this can end up causing confusion regarding the order that
things are run, and test pollution. To improve the readability and
accuracy of your Mocha tests, use `beforeEach()` and `afterEach()`
instead, wherever possible.
  • Loading branch information
lencioni committed Sep 6, 2016
1 parent 4b79633 commit aeb6c7f
Show file tree
Hide file tree
Showing 5 changed files with 275 additions and 1 deletion.
1 change: 1 addition & 0 deletions docs/rules/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@
* [no-hooks-for-single-case](no-hooks-for-single-case.md) - disallow hooks for a single test or test suite
* [no-top-level-hooks](no-top-level-hooks.md) - disallow top-level hooks
* [no-identical-title](no-identical-title.md) - disallow identical titles
* [no-before-after](no-before-after.md) - disallow `before` and `after` in favor of `beforeEach` and `afterEach`
29 changes: 29 additions & 0 deletions docs/rules/no-before-after.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Disallow `before` and `after` in favor of `beforeEach` and `afterEach`

`before()` and `after()` run once before and after all examples. In practice, this can end up causing confusion regarding the order that things are run, and test pollution. To improve the readability and accuracy of your Mocha tests, use `beforeEach()` and `afterEach()` instead, wherever possible.

## Rule Details

The following patterns are considered warnings:

```js
before(() => {

});

after(function () {

});
```

The following patterns are not warnings:

```js
beforeEach(() => {

});

afterEach(function () {

});
```
3 changes: 2 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ module.exports = {
'no-hooks-for-single-case': require('./lib/rules/no-hooks-for-single-case'),
'no-sibling-hooks': require('./lib/rules/no-sibling-hooks'),
'no-top-level-hooks': require('./lib/rules/no-top-level-hooks'),
'no-identical-title': require('./lib/rules/no-identical-title')
'no-identical-title': require('./lib/rules/no-identical-title'),
'no-before-after': require('./lib/rules/no-before-after')
},
configs: {
recommended: {
Expand Down
83 changes: 83 additions & 0 deletions lib/rules/no-before-after.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
'use strict';

var BEFORE_OR_AFTER = [ 'before', 'after' ],
CONTEXT_OR_DESCRIBE = [ 'context', 'describe', 'xcontext', 'xdescribe' ];

function isNodeBeforeOrAfterCall(node) {
var methodName, callee = node.callee;

if (callee.type === 'MemberExpression') {
// This is foo.before(), not before()
return false;
}

methodName = callee.name;

return BEFORE_OR_AFTER.indexOf(methodName) !== -1;
}

// eslint-disable-next-line complexity, max-statements
function isInContextOrDescribe(node) {
// Recurse up the tree, looking for a CallExpression with a callee of
// `context` or `describe`.

if (!node) {
// We've reached the top of the tree and didn't find what we were looking
// for.
return false;
}

if (node.type === 'CallExpression') {
if (node.callee.type === 'MemberExpression') {
if (CONTEXT_OR_DESCRIBE.indexOf(node.callee.property.name) !== -1) {
return true;
}

if (
node.callee.property.name === 'skip' &&
CONTEXT_OR_DESCRIBE.indexOf(node.callee.object.name) !== -1
) {
return true;
}

return isInContextOrDescribe(node.parent);
}

if (CONTEXT_OR_DESCRIBE.indexOf(node.callee.name) !== -1) {
return true;
}
}

return isInContextOrDescribe(node.parent);
}

module.exports = {
meta: {
docs: {},

schema: []
},

create: function rule(context) {
return {
CallExpression: function Callexpression(node) {
var methodName;

if (!isNodeBeforeOrAfterCall(node)) {
return;
}

if (!isInContextOrDescribe(node)) {
return;
}

methodName = node.callee.name;
context.report(
node,
'Use `' + methodName + 'Each` instead of `' + methodName + '` because `' + methodName
+ '` will run only once for all tests and we want setup and teardowns to happen once for each test'
);
}
};
}
};
160 changes: 160 additions & 0 deletions test/rules/no-before-after.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
'use strict';

var rule = require('../../lib/rules/no-before-after'),
RuleTester = require('eslint').RuleTester,
ruleTester = new RuleTester(),

beforeErrors = [ {
// eslint-disable-next-line max-len
message: 'Use `beforeEach` instead of `before` because `before` will run only once for all tests and we want setup and teardowns to happen once for each test',
type: 'CallExpression'
} ],

afterErrors = [ {
// eslint-disable-next-line max-len
message: 'Use `afterEach` instead of `after` because `after` will run only once for all tests and we want setup and teardowns to happen once for each test',
type: 'CallExpression'
} ];

ruleTester.run('no-before-after', rule, {

valid: [
{ code: 'before(function() {})' },
{ code: 'after(function() {})' },
{ code: 'beforeEach(function() {})' },
{ code: 'afterEach(function() {})' },
{ code: 'foo.before(function() {})' },
{ code: 'foo.after(function() {})' }
],

invalid: [
{
code: 'describe(function() { before(function() {}) })',
errors: beforeErrors
},
{
code: 'context(function() { before(function() {}) })',
errors: beforeErrors
},
{
code: 'xdescribe(function() { before(function() {}) })',
errors: beforeErrors
},
{
code: 'xcontext(function() { before(function() {}) })',
errors: beforeErrors
},
{
code: 'describe.skip(function() { before(function() {}) })',
errors: beforeErrors
},
{
code: 'context.skip(function() { before(function() {}) })',
errors: beforeErrors
},
{
code: 'xdescribe.skip(function() { before(function() {}) })',
errors: beforeErrors
},
{
code: 'xcontext.skip(function() { before(function() {}) })',
errors: beforeErrors
},
{
code: 'wrap().withFoo().describe(function() { before(function() {}) })',
errors: beforeErrors
},
{
code: 'wrap().withFoo().context(function() { before(function() {}) })',
errors: beforeErrors
},
{
code: 'wrap().withFoo().xdescribe(function() { before(function() {}) })',
errors: beforeErrors
},
{
code: 'wrap().withFoo().xcontext(function() { before(function() {}) })',
errors: beforeErrors
},
{
code: 'wrap().withFoo().skip().describe(function() { before(function() {}) })',
errors: beforeErrors
},
{
code: 'wrap().withFoo().skip().context(function() { before(function() {}) })',
errors: beforeErrors
},
{
code: 'wrap().withFoo().skip().xdescribe(function() { before(function() {}) })',
errors: beforeErrors
},
{
code: 'wrap().withFoo().skip().xcontext(function() { before(function() {}) })',
errors: beforeErrors
},
{
code: 'describe(function() { after(function() {}) })',
errors: afterErrors
},
{
code: 'context(function() { after(function() {}) })',
errors: afterErrors
},
{
code: 'xdescribe(function() { after(function() {}) })',
errors: afterErrors
},
{
code: 'xcontext(function() { after(function() {}) })',
errors: afterErrors
},
{
code: 'describe.skip(function() { after(function() {}) })',
errors: afterErrors
},
{
code: 'context.skip(function() { after(function() {}) })',
errors: afterErrors
},
{
code: 'xdescribe.skip(function() { after(function() {}) })',
errors: afterErrors
},
{
code: 'xcontext.skip(function() { after(function() {}) })',
errors: afterErrors
},
{
code: 'wrap().withFoo().describe(function() { after(function() {}) })',
errors: afterErrors
},
{
code: 'wrap().withFoo().context(function() { after(function() {}) })',
errors: afterErrors
},
{
code: 'wrap().withFoo().xdescribe(function() { after(function() {}) })',
errors: afterErrors
},
{
code: 'wrap().withFoo().xcontext(function() { after(function() {}) })',
errors: afterErrors
},
{
code: 'wrap().withFoo().skip().describe(function() { after(function() {}) })',
errors: afterErrors
},
{
code: 'wrap().withFoo().skip().context(function() { after(function() {}) })',
errors: afterErrors
},
{
code: 'wrap().withFoo().skip().xdescribe(function() { after(function() {}) })',
errors: afterErrors
},
{
code: 'wrap().withFoo().skip().xcontext(function() { after(function() {}) })',
errors: afterErrors
}
]
});

0 comments on commit aeb6c7f

Please sign in to comment.