diff --git a/README.md b/README.md
index d77d48c1f..0c6fd93b3 100644
--- a/README.md
+++ b/README.md
@@ -118,6 +118,7 @@ for more information about extending configuration files.
 | [valid-describe][]           | Enforce valid `describe()` callback                               | ![recommended][] |                     |
 | [valid-expect-in-promise][]  | Enforce having return statement when testing with promises        | ![recommended][] |                     |
 | [valid-expect][]             | Enforce valid `expect()` usage                                    | ![recommended][] |                     |
+| [prefer-todo][]              | Suggest using `test.todo()`                                       |                  | ![fixable-green][]  |
 
 ## Credit
 
@@ -151,6 +152,7 @@ for more information about extending configuration files.
 [valid-describe]: docs/rules/valid-describe.md
 [valid-expect-in-promise]: docs/rules/valid-expect-in-promise.md
 [valid-expect]: docs/rules/valid-expect.md
+[prefer-todo]: docs/rules/prefer-todo.md
 [fixable-green]: https://img.shields.io/badge/-fixable-green.svg
 [fixable-yellow]: https://img.shields.io/badge/-fixable-yellow.svg
 [recommended]: https://img.shields.io/badge/-recommended-lightgrey.svg
diff --git a/docs/rules/prefer-todo.md b/docs/rules/prefer-todo.md
new file mode 100644
index 000000000..322fec17e
--- /dev/null
+++ b/docs/rules/prefer-todo.md
@@ -0,0 +1,28 @@
+# Suggest using `test.todo` (prefer-todo)
+
+When test cases are empty then it is better to mark them as `test.todo` as it
+will be highlighted in the summary output.
+
+## Rule details
+
+This rule triggers a warning if empty test case is used without 'test.todo'.
+
+```js
+test('i need to write this test');
+```
+
+### Default configuration
+
+The following pattern is considered warning:
+
+```js
+test('i need to write this test'); // Unimplemented test case
+test('i need to write this test', () => {}); // Empty test case body
+test.skip('i need to write this test', () => {}); // Empty test case body
+```
+
+The following pattern is not warning:
+
+```js
+test.todo('i need to write this test');
+```
diff --git a/index.js b/index.js
index bff8518c6..fb3afbfcf 100644
--- a/index.js
+++ b/index.js
@@ -27,6 +27,7 @@ const requireTothrowMessage = require('./rules/require-tothrow-message');
 const noAliasMethods = require('./rules/no-alias-methods');
 const noTestCallback = require('./rules/no-test-callback');
 const noTruthyFalsy = require('./rules/no-truthy-falsy');
+const preferTodo = require('./rules/prefer-todo');
 
 const snapshotProcessor = require('./processors/snapshot-processor');
 
@@ -114,5 +115,6 @@ module.exports = {
     'no-alias-methods': noAliasMethods,
     'no-test-callback': noTestCallback,
     'no-truthy-falsy': noTruthyFalsy,
+    'prefer-todo': preferTodo,
   },
 };
diff --git a/rules/__tests__/prefer-todo.test.js b/rules/__tests__/prefer-todo.test.js
new file mode 100644
index 000000000..d35e34b2f
--- /dev/null
+++ b/rules/__tests__/prefer-todo.test.js
@@ -0,0 +1,59 @@
+'use strict';
+
+const { RuleTester } = require('eslint');
+const rule = require('../prefer-todo');
+
+const ruleTester = new RuleTester({
+  parserOptions: { ecmaVersion: 2015 },
+});
+
+ruleTester.run('prefer-todo', rule, {
+  valid: [
+    'test.todo("i need to write this test");',
+    'test(obj)',
+    'fit("foo")',
+    'xit("foo")',
+    'test("stub", () => expect(1).toBe(1));',
+    `
+      supportsDone && params.length < test.length
+        ? done => test(...params, done)
+        : () => test(...params);
+    `,
+  ],
+  invalid: [
+    {
+      code: `test("i need to write this test");`,
+      errors: [
+        { message: 'Prefer todo test case over unimplemented test case' },
+      ],
+      output: 'test.todo("i need to write this test");',
+    },
+    {
+      code: 'test(`i need to write this test`);',
+      errors: [
+        { message: 'Prefer todo test case over unimplemented test case' },
+      ],
+      output: 'test.todo(`i need to write this test`);',
+    },
+    {
+      code: 'it("foo", function () {})',
+      errors: ['Prefer todo test case over empty test case'],
+      output: 'it.todo("foo")',
+    },
+    {
+      code: 'it("foo", () => {})',
+      errors: ['Prefer todo test case over empty test case'],
+      output: 'it.todo("foo")',
+    },
+    {
+      code: `test.skip("i need to write this test", () => {});`,
+      errors: ['Prefer todo test case over empty test case'],
+      output: 'test.todo("i need to write this test");',
+    },
+    {
+      code: `test.skip("i need to write this test", function() {});`,
+      errors: ['Prefer todo test case over empty test case'],
+      output: 'test.todo("i need to write this test");',
+    },
+  ],
+});
diff --git a/rules/prefer-todo.js b/rules/prefer-todo.js
new file mode 100644
index 000000000..f75e74570
--- /dev/null
+++ b/rules/prefer-todo.js
@@ -0,0 +1,78 @@
+'use strict';
+
+const {
+  getDocsUrl,
+  isFunction,
+  composeFixers,
+  getNodeName,
+  isString,
+} = require('./util');
+
+function isOnlyTestTitle(node) {
+  return node.arguments.length === 1;
+}
+
+function isFunctionBodyEmpty(node) {
+  return node.body.body && !node.body.body.length;
+}
+
+function isTestBodyEmpty(node) {
+  const fn = node.arguments[1]; // eslint-disable-line prefer-destructuring
+  return fn && isFunction(fn) && isFunctionBodyEmpty(fn);
+}
+
+function addTodo(node, fixer) {
+  const testName = getNodeName(node.callee)
+    .split('.')
+    .shift();
+  return fixer.replaceText(node.callee, `${testName}.todo`);
+}
+
+function removeSecondArg({ arguments: [first, second] }, fixer) {
+  return fixer.removeRange([first.range[1], second.range[1]]);
+}
+
+function isFirstArgString({ arguments: [firstArg] }) {
+  return firstArg && isString(firstArg);
+}
+
+const isTestCase = node =>
+  node &&
+  node.type === 'CallExpression' &&
+  ['it', 'test', 'it.skip', 'test.skip'].includes(getNodeName(node.callee));
+
+function create(context) {
+  return {
+    CallExpression(node) {
+      if (isTestCase(node) && isFirstArgString(node)) {
+        const combineFixers = composeFixers(node);
+
+        if (isTestBodyEmpty(node)) {
+          context.report({
+            message: 'Prefer todo test case over empty test case',
+            node,
+            fix: combineFixers(removeSecondArg, addTodo),
+          });
+        }
+
+        if (isOnlyTestTitle(node)) {
+          context.report({
+            message: 'Prefer todo test case over unimplemented test case',
+            node,
+            fix: combineFixers(addTodo),
+          });
+        }
+      }
+    },
+  };
+}
+
+module.exports = {
+  create,
+  meta: {
+    docs: {
+      url: getDocsUrl(__filename),
+    },
+    fixable: 'code',
+  },
+};
diff --git a/rules/util.js b/rules/util.js
index c3141e0fa..2db81c0d6 100644
--- a/rules/util.js
+++ b/rules/util.js
@@ -130,6 +130,10 @@ const isDescribe = node =>
 const isFunction = node =>
   node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression';
 
+const isString = node =>
+  (node.type === 'Literal' && typeof node.value === 'string') ||
+  node.type === 'TemplateLiteral';
+
 /**
  * Generates the URL to documentation for the given rule name. It uses the
  * package version to build the link to a tagged version of the
@@ -182,6 +186,14 @@ const scopeHasLocalReference = (scope, referenceName) => {
   );
 };
 
+function composeFixers(node) {
+  return (...fixers) => {
+    return fixerApi => {
+      return fixers.reduce((all, fixer) => [...all, fixer(node, fixerApi)], []);
+    };
+  };
+}
+
 module.exports = {
   method,
   method2,
@@ -199,6 +211,8 @@ module.exports = {
   isDescribe,
   isFunction,
   isTestCase,
+  isString,
   getDocsUrl,
   scopeHasLocalReference,
+  composeFixers,
 };