Skip to content
New issue

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

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

Already on GitHub? # to your account

Introduce a lint rule to report error when testing promises. If a exp… #42

Merged
merged 19 commits into from
Jan 17, 2018

Conversation

tushardhole
Copy link
Contributor

@tushardhole tushardhole commented Dec 30, 2017

…ectation is added inside a then or catch block of promise then as per jest docs, it is mandatory to return that promise for succesfull execution of that expectation. This rull will report all such expectations where promise is not returned.

Summary
Implements #41

As jest user I work on a project that heavily uses promises and jest is our tool to write tests. We were aware that as per jest docs, it is required to return a promise, if you want to test some thing in its fulfillment or rejection block.

If you do not return that promise and write some exceptions in its then or catch block. Then that test will always be green, as the expectation in then or catch block is never executed. If you return that promise then the expectation will be executed and tests will be red or green depending on the actual result of that expectation.

In this situation it becomes very important to make sure that,
a) You make that test red before making it green for the final push. Which will assure that the exception is getting executed.

As human, we may forget to do that and hence this lint rule will be helpful.

Test plan

  1. Added a unit test case with many combinations.
  2. Tested on an existing project which uses jest & promises.
  3. Ran all tests using 'yarn test' command

Limitations

  1. If the expectation inside then or catch is a expect statement then this rule will work correctly. In case, there is call expression to some function, from then or catch block and that function has the expect statement then this rule will not be able to detect that. I will work on that issue but I feel that as 'Good to Have' and should be next step to improve this rule and current PR changes can be very well considered as 'Step 1' which is very useful in its current state.

@tushardhole tushardhole changed the title Introduce a lint rule to report error when testing promises. If a expectation is added inside a then or catch block of promise then as per jest docs, it is required to return that promise for successful execution of that expectation. This rule will report all such expectations where promise is not returned. Introduce a lint rule to report error when testing promises. If a exp… Dec 30, 2017
@thymikee thymikee requested a review from SimenB January 2, 2018 21:34
@SimenB
Copy link
Member

SimenB commented Jan 5, 2018

CI is failing because of non-semantic commit message. See http://karma-runner.github.io/2.0/dev/git-commit-msg.html

'function() { /*fulfillment*/}, ' +
'function() { /*rejection*/ expect(someThing).toEqual(true)})})',

'it("it1", function() { return somePromise.then()})',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add assertions that await is ok without return?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will work on that.

@tushardhole
Copy link
Contributor Author

I will work on fixing non-semantic commit message.

introduce a lint rule to report error when testing promises. If a expectation is added inside a then or catch block of promise then as per jest docs,
it is mandatory to return that promise for succesfull execution of that expectation. This rule will report all such expectations where promise is not returned.
@tushardhole tushardhole force-pushed the valid_expect_in_promise branch from 63da944 to da393e0 Compare January 10, 2018 13:05
@tushardhole
Copy link
Contributor Author

@SimenB I have done the semantic commit message fix and a new commit to allow await expressions without return statement

Copy link
Member

@SimenB SimenB left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs docs

@tushardhole
Copy link
Contributor Author

@SimenB doc changes done! Please review the same.

Also did some sanity on a project which uses promises and jest. Found one bug with nested then or catch, added unit test & fixed 😊

Copy link
Member

@SimenB SimenB left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ran this against Jest's code base, and there are a couple of false positives.

  1. arrow shorthand: https://github.com/facebook/jest/blob/3ffc99e02d4b1ecad9d0f5ec2e228bddc8e7210b/packages/jest-runtime/src/__tests__/runtime_module_directories.test.js#L57-L66
    1. There are many cases here where it doesn't warn even though the tests use the same pattern. Why?
  2. A promise which is created, but only returned at a later time: https://github.com/facebook/jest/blob/3ffc99e02d4b1ecad9d0f5ec2e228bddc8e7210b/packages/jest-haste-map/src/crawlers/__tests__/node.test.js#L103-L134

@tushardhole
Copy link
Contributor Author

@SimenB thanks for that feedback! I will work on that.

@tushardhole
Copy link
Contributor Author

tushardhole commented Jan 13, 2018

@SimenB When i tried to run the rule against jest, the rule was not running at all, I believe following was the cause,

In line no 21 of .eslintrc.js in jest code base, it says to check only .md files, I.e. files: ['*.md'],
when I changed the line to below, the rule started reporting,
files: ['*.js', '*.md'],

To summarize I did following steps to test this rule agains jest code base,

  1. Copied index.js and rules dir from eslint-plugin-jest to node_modules/eslint-plugin-jest/ of jest code base

  2. Changed the block on line 20 in .eslintrc.js to this,

    {
      files: ['*.js', '*.md'],
      rules: {
        'consistent-return': 0,
        'flowtype/require-valid-file-annotation': 0,
        'import/no-extraneous-dependencies': 0,
        'import/no-unresolved': 0,
        "jest/valid-expect-in-promise": 2,
        'jest/no-focused-tests': 0,
        'jest/no-identical-title': 0,
        'no-undef': 0,
        'no-unused-vars': 0,
        'prettier/prettier': 0,
        'react/jsx-no-undef': 0,
        'react/react-in-jsx-scope': 0,
        'sort-keys': 0,
        'unicorn/filename-case': 0,
      },
    }

Here is the output,

/Users/tushard/OpenProjects/jest/packages/jest-haste-map/src/crawlers/__tests__/node.test.js
  103:11  error  Promise should be returned to test its fulfillment or rejection  jest/valid-expect-in-promise

/Users/tushard/OpenProjects/jest/packages/jest-runtime/src/__tests__/runtime_module_directories.test.js
  57:48  error  Promise should be returned to test its fulfillment or rejection  jest/valid-expect-in-promise

/Users/tushard/OpenProjects/jest/packages/jest-runtime/src/__tests__/runtime_require_mock.test.js
  133:58  error  Promise should be returned to test its fulfillment or rejection  jest/valid-expect-in-promise

/Users/tushard/OpenProjects/jest/packages/jest-runtime/src/__tests__/runtime_require_module.test.js
  134:56  error  Promise should be returned to test its fulfillment or rejection  jest/valid-expect-in-promise
  159:54  error  Promise should be returned to test its fulfillment or rejection  jest/valid-expect-in-promise

Let me know your thoughts, we may need to add .js in .eslintrc.js of jest

@SimenB
Copy link
Member

SimenB commented Jan 13, 2018

@tushardhole
Copy link
Contributor Author

@SimenB Thanks, I will test the rule that way.

On other hand , I don't find the previousaly mentioned false positives in the output posted in my previous comment. Do you find that as the expectated output is or something is missing in that.

@SimenB
Copy link
Member

SimenB commented Jan 13, 2018

On other hand , I don't find the previousaly mentioned false positives in the output posted in my previous comment.

They are in your paste, in particular

/Users/tushard/OpenProjects/jest/packages/jest-haste-map/src/crawlers/__tests__/node.test.js
  103:11  error  Promise should be returned to test its fulfillment or rejection  jest/valid-expect-in-promise

and

/Users/tushard/OpenProjects/jest/packages/jest-runtime/src/__tests__/runtime_module_directories.test.js
  57:48  error  Promise should be returned to test its fulfillment or rejection  jest/valid-expect-in-promise

@SimenB
Copy link
Member

SimenB commented Jan 14, 2018

I'm having issues creating a test for the case of arrow shorthand, but I pushed up a test for one of the failing cases at least.

I was being dumb, two failing test cases which should be valid pushed

@SimenB SimenB force-pushed the valid_expect_in_promise branch from 6f02ae9 to d1416c2 Compare January 14, 2018 23:01
@SimenB SimenB force-pushed the valid_expect_in_promise branch from d1416c2 to 5b25a69 Compare January 14, 2018 23:03
`
it('shorthand arrow', () =>
something().then(value => {
expect(() => {
Copy link
Member

@SimenB SimenB Jan 14, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this passes if I add return before expect (which is not needed)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will work on this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are two more similar cases,

  1. Where expect is inside a block,
test('invalid return', () => {
       const promise = something().then(value => {
         { expect(value).toBe('red'); }
    });
 });
  1. Where expect is inside another method,
test('invalid return', () => {
       const promise = something().then(value => {
         anotherMethod(); //this method contains the expect stament
    });
 });

I have already added this limitation(#>2) in the PR description. I think these are rare scenarios, the first one may not occur at all. As mentioned earlier in the PR description, I think they are good to have can be done in next enhancement to this rule.
If this scenarios are must to have , then I am happy to implement in this PR itself.

'it("it1", function() { Promise.resolve().then(' +
'function() { /*fulfillment*/ expect(someThing).toEqual(true)}, ' +
'function() { /*rejection*/ expect(someThing).toEqual(true)})})',
errors: [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this 2 errors? It should just be one, there is only a single promise here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will fix that.

@SimenB
Copy link
Member

SimenB commented Jan 14, 2018

Another false positive is if I use the done callback.

test('callback in handler', done => {
 something().then(value => {
   expect(value).toBe('red');
   
   done();
  });
});

This shouldn't warn, but it's also weird usage of promises, so I think we should just not warn here. (if it's easy to solve, please do so)

@tushardhole
Copy link
Contributor Author

tushardhole commented Jan 15, 2018

This shouldn't warn, but it's also weird usage of promises, so I think we should just not warn here. (if it's easy to solve, please do so)

Yes I will do that. I think, this rule should just ignore processing if the test function have done parameter. Also i verified, the name of parameter can be anything not necessary to be named as done only.

Whether that done function is actually called or not, should not be verified by this rule. We can add a different rule for that like prefer-calling-done, thoughts ?

Verification of done getting called is not required, the test will fail if the function signature has done param, and test does not call it.
Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.

@tushardhole
Copy link
Contributor Author

tushardhole commented Jan 17, 2018

@SimenB below feedback are fixed,

  1. short hand arrow function ✅
  2. later promise return ✅
  3. done async callback ✅
  4. duplicate errors for same promise ✅

Copy link
Member

@SimenB SimenB left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great, thank you!

@SimenB SimenB merged commit eb31a54 into jest-community:master Jan 17, 2018
@SimenB
Copy link
Member

SimenB commented Jan 17, 2018

Ran it on the jest code base, and it actually found an error! jestjs/jest#5336

Great job!

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants