diff --git a/docs/rules/jest/no-if.md b/docs/rules/jest/no-if.md new file mode 100644 index 00000000..009bc364 --- /dev/null +++ b/docs/rules/jest/no-if.md @@ -0,0 +1,34 @@ +# Prevent if statements in tests. (jest/no-if) +This rule prevents the use of if statements in tests. + +## Rule Details + +If statments in tests is usually an indication that a test is attempting to cover too much. Each branch of code executing within an if statement will likely be better served by a test devoted to it. + +Examples of **incorrect** code for this rule: + +```js +it('foo', () => { + if('bar') { + // an if statement here is invalid + // you are probably testing too much + } +}) + +``` + +Examples of **correct** code for this rule: + +```js +it('foo', () => { + // only test the 'foo' case +}) + +it('bar', () => { + // test the 'bar' case separately +}) +``` + +## When Not To Use It + +If you do not wish to prevent the use of if statements in tests, you can safely disable this rule. diff --git a/lib/rules/jest/no-if.js b/lib/rules/jest/no-if.js new file mode 100644 index 00000000..bcf9e82b --- /dev/null +++ b/lib/rules/jest/no-if.js @@ -0,0 +1,93 @@ +const {docsUrl} = require('../../utilities'); + +const TEST_FUNCTION_NAMES = [ + 'it', + 'xit', + 'fit', + 'test', + 'xtest', + 'describe', + 'fdescribe', + 'xdescribe', +]; + +module.exports = { + meta: { + docs: { + description: 'Prevent if statements in tests.', + category: 'Best Practices', + recommended: false, + uri: docsUrl('jest/no-if'), + }, + messages: { + noIf: [ + 'Tests should not contain if statements.', + 'This is usually an indication that you', + 'are attempting to test too much at once', + 'and may want to consider breaking the if', + 'statement out into a separate test.', + ].join(' '), + }, + }, + + create(context) { + let inCallExpression = false; + + return { + CallExpression(node) { + if (notTestFunction(node) || hasEmptyBody(node)) { + return; + } + inCallExpression = true; + }, + IfStatement(node) { + if (!inCallExpression) { + return; + } + + context.report({ + messageId: 'noIf', + node, + }); + }, + 'CallExpression:exit': () => { + inCallExpression = false; + }, + }; + }, +}; + +function notTestFunction(node) { + const method = getMethodName(node); + return !matchTestFunctionName(method); +} + +function matchTestFunctionName(functionName) { + return TEST_FUNCTION_NAMES.includes(functionName); +} + +function hasEmptyBody(node) { + return ( + node.arguments && + node.arguments.length === 2 && + node.arguments[1].type === 'ArrowFunctionExpression' && + node.arguments[1].body && + node.arguments[1].body.body && + node.arguments[1].body.body.length === 0 + ); +} + +function getMethodName({callee}) { + switch (callee.type) { + case 'CallExpression': + return callee.callee.object + ? callee.callee.object.name + : callee.callee.name; + case 'Identifier': + return callee.name; + case 'MemberExpression': + return callee.object.name; + default: + return false; + } +} diff --git a/tests/lib/rules/jest/no-if.js b/tests/lib/rules/jest/no-if.js new file mode 100644 index 00000000..a2f9f92e --- /dev/null +++ b/tests/lib/rules/jest/no-if.js @@ -0,0 +1,240 @@ +const {RuleTester} = require('eslint'); +const rule = require('../../../../lib/rules/jest/no-if'); +require('babel-eslint'); + +const parser = 'babel-eslint'; +const ruleTester = new RuleTester(); + +ruleTester.run('no-if', rule, { + valid: [ + { + code: `if(foo) {}`, + }, + { + code: `it('foo', () => {})`, + parser, + }, + { + code: `foo('bar', () => { + if(baz) {} + })`, + parser, + }, + ], + invalid: [ + { + code: `it('foo', () => { + if('bar') {} + })`, + parser, + errors: [ + { + messageId: 'noIf', + }, + ], + }, + { + code: `it.skip('foo', () => { + if('bar') {} + })`, + parser, + errors: [ + { + messageId: 'noIf', + }, + ], + }, + { + code: `it.only('foo', () => { + if('bar') {} + })`, + parser, + errors: [ + { + messageId: 'noIf', + }, + ], + }, + { + code: `xit('foo', () => { + if('bar') {} + })`, + parser, + errors: [ + { + messageId: 'noIf', + }, + ], + }, + { + code: `fit('foo', () => { + if('bar') {} + })`, + parser, + errors: [ + { + messageId: 'noIf', + }, + ], + }, + { + code: `describe('foo', () => { + if('bar') {} + })`, + parser, + errors: [ + { + messageId: 'noIf', + }, + ], + }, + { + code: `describe.skip('foo', () => { + if('bar') {} + })`, + parser, + errors: [ + { + messageId: 'noIf', + }, + ], + }, + { + code: `describe.only('foo', () => { + if('bar') {} + })`, + parser, + errors: [ + { + messageId: 'noIf', + }, + ], + }, + { + code: `xdescribe.only('foo', () => { + if('bar') {} + })`, + parser, + errors: [ + { + messageId: 'noIf', + }, + ], + }, + { + code: `fdescribe.only('foo', () => { + if('bar') {} + })`, + parser, + errors: [ + { + messageId: 'noIf', + }, + ], + }, + { + code: `test('foo', () => { + if('bar') {} + })`, + parser, + errors: [ + { + messageId: 'noIf', + }, + ], + }, + { + code: `test.skip('foo', () => { + if('bar') {} + })`, + parser, + errors: [ + { + messageId: 'noIf', + }, + ], + }, + { + code: `test.only('foo', () => { + if('bar') {} + })`, + parser, + errors: [ + { + messageId: 'noIf', + }, + ], + }, + { + code: `xtest('foo', () => { + if('bar') {} + })`, + parser, + errors: [ + { + messageId: 'noIf', + }, + ], + }, + { + code: `ftest('foo', () => { + if('bar') {} + })`, + parser, + errors: [ + { + messageId: 'noIf', + }, + ], + }, + { + code: `describe('foo', () => { + it('bar', () => { + if('bar') {} + }) + })`, + parser, + errors: [ + { + messageId: 'noIf', + }, + ], + }, + { + code: `describe('foo', () => { + it('bar', () => { + if('bar') {} + }) + it('baz', () => { + if('qux') {} + if('quux') {} + }) + })`, + parser, + errors: [ + { + messageId: 'noIf', + }, + { + messageId: 'noIf', + }, + { + messageId: 'noIf', + }, + ], + }, + { + code: `describe('foo', () => { + if('bar') {} + }) + if('baz') {} + `, + parser, + errors: [ + { + messageId: 'noIf', + }, + ], + }, + ], +}); diff --git a/yarn.lock b/yarn.lock index b79ad7db..acc66163 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3320,10 +3320,10 @@ mem@^1.1.0: dependencies: mimic-fn "^1.0.0" -merge@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da" - integrity sha1-dTHjnUlJwoGma4xabgJl6LBYlNo= +merge@1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.1.tgz#38bebf80c3220a8a487b6fcfb3941bb11720c145" + integrity sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ== micromatch@^2.1.5: version "2.3.11"