Skip to content

[feat] Jest mocks, AsyncStorage testing guidelines #53

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

Merged
merged 2 commits into from
Apr 8, 2019
Merged

Conversation

krizzu
Copy link
Member

@krizzu krizzu commented Apr 1, 2019

Summary:

We had some issues with Async Storage when it comes to testing with Jest. This PR adds mocks to be used while testing, to avoid error and integrate AS better.

Also some updates to README/Error thrown when module is not linked.

Small fix for CI job.

This PR will close:
#51, #39, #12

Test Plan:

Green CI, test it with an app.

@krizzu krizzu added the enhancement New feature or request label Apr 1, 2019
@krizzu krizzu changed the title [feat] Jest mocks, docs to help testing with Jest [feat] Jest mocks, AsyncStorage testing guidelines Apr 1, 2019
Copy link

@thymikee thymikee left a comment

Choose a reason for hiding this comment

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

Nice! Looks like a good start, but we can make it better :) Left notes inlined


import AsyncStorageMock from '@react-native-community/async-storage/jest/async-storage-mock';

jest.doMock('react-native', () => {
Copy link

Choose a reason for hiding this comment

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

I think it's better to use jest.mock, because it's more widely used

2. Inside that folder, create `react-native.js` file.
3. Inside that file, import `AsyncStorageMock` and use it to mock `NativeModules`.


Copy link

Choose a reason for hiding this comment

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

FYI you can run Prettier on .md files to remove excess whitespace :)

// other config
"setupFiles": "./path/to/jestSetupFile.js"
},
// ...
Copy link

Choose a reason for hiding this comment

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

This is not a valid JSON. Also setupFiles accepts an array. Please fix to:

{
  "jest": {
    "setupFiles": ["./path/to/jestSetupFile.js"]
  }
}

```json
"jest": {
// ...
"transformIgnorePatterns": ["/node_modules/@react-native-community/async-storage/(?!(lib))"]
Copy link

Choose a reason for hiding this comment

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

  1. Make it valid JSON
  2. Use a catch-all regex for all @react-native-community packages to help others :D :
"transformIgnorePatterns": ["node_modules/(?!react-native|@react-native-community)"]
  1. Make a note that this should be included by default in RN 0.60

Copy link

Choose a reason for hiding this comment

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

I don't see this addressed, mind fixing?

/**
* Clears all keys in storage
*/
clear: (cb: CallbackType) => cb(),
Copy link

Choose a reason for hiding this comment

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

So the mock is actually test-framework agnostic. Maybe it's nice, but I think most of the users would expect this mock to be fully functional in JS-land only, plus it would be nice if it used jest.fn to mock methods, because that would make them, well, mocks :P which you can inspect.

How about getting rid of the example mock and just provide the basic implementation + mocks here?

Copy link
Member Author

Choose a reason for hiding this comment

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

That was my dilemma here - to provide fully functional mock or simple one + docs about providing your own functionality. I decided to do the latter, as I thought most people would use it to pass tests.

But I think you might be right here - it's much easier for users to implement it other way around. Going to make it fully functional first.

Also, I had jest.fn at first, but then I thought about accessing this mock in your test - how would you do that? You'd have to import mock into your test and check, i.e if it was called with such arguments. Or am I missing something here?

Copy link

Choose a reason for hiding this comment

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

If you substitute module with a mock like RN.AsyncStorage = ASMock;, AsyncStorage should be a mock then and you'll be able to do expect(AsyncStorage.getItem).toBeCalledWith(smth)

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, but this is mock for NativeModule, not something that user has access too


To fix this issue try these steps:
• Run \`react-native link @react-native-community/async-storage\` in the project root.
• Rebuild and re-run the app.
• Restart the packager with \`--clearCache\` flag.
• If you are using CocoaPods on iOS, run \`pod install\` in the \`ios\` directory and then rebuild and re-run the app.
• If this error shows up while testing with Jest, check out how to mock Async Storage in repository docs.
Copy link

Choose a reason for hiding this comment

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

it would be nice to provide a link

package.json Outdated
@@ -54,7 +54,8 @@
"semantic-release": "15.13.3"
},
"jest": {
"preset": "react-native"
"preset": "react-native",
"testMatch": [ "**/__tests__/**/*.[jt]s?(x)", "**/?(*.)+(test).[jt]s?(x)" ]
Copy link

Choose a reason for hiding this comment

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

this looks pretty much lika a default testMatch, do you really need to change it?. Also, format with Prettier

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point, I left .spec out, for e2e tests. Going to change that.

@krizzu
Copy link
Member Author

krizzu commented Apr 3, 2019

@thymikee
This is rather large change from what I had before - now JS interface is mocked instead of NativeModule.

All of public facing methods of the mock is not jest.fn, meaning more test cases can be checked.

Also, added unit test to check mock itself, for both Promise and Callback functionality.

Thanks for your review so far and please let me know what you think 🙏

thymikee
thymikee previously approved these changes Apr 7, 2019
Copy link

@thymikee thymikee left a comment

Choose a reason for hiding this comment

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

👍 Feel free to merge after the nits are addressed :)


Select a method that suits your needs:

### Mock `node_modules`
Copy link

Choose a reason for hiding this comment

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

Change to With __mocks__ directory

@@ -0,0 +1,77 @@
# Jest integration

Async Storage module is tighly coupled with a `Native Module`, meaning it needs a running React Native application to work properly. In order to use it in tests, you have to provide its separate implementation. Follow those steps to add mocked `Async Storage` to your test cases.
Copy link

Choose a reason for hiding this comment

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

Native Module -> NativeModule

Each public method available from `Async Storage` is [a mock function](https://jestjs.io/docs/en/mock-functions), that you can test for certain condition, for example, check if it has been called with a specific arguments:

```javascript
it('checks if Async Storage is used', async () => {
Copy link

Choose a reason for hiding this comment

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

wonder why the indentation? Are you using Prettier to format that? If not, I definitely recommend

```json
"jest": {
// ...
"transformIgnorePatterns": ["/node_modules/@react-native-community/async-storage/(?!(lib))"]
Copy link

Choose a reason for hiding this comment

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

I don't see this addressed, mind fixing?

expect(value1).toBeNull();
expect(value2).toBeNull();
done();
}).catch(e => done.fail(e));
Copy link

Choose a reason for hiding this comment

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

you're using callback approach, but here taking advantage of getItem returning a promise. You should use the first argument (named _ instead of err or error) and check if it's not null, then call done.fail

Copy link
Member Author

Choose a reason for hiding this comment

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

The reason why I'm catching some of the block is that error thrown in callback methods are swallowed by Jest - meaning it'll get timeout singal, without specific reason why caused that.

Note that I only wrap blocks with assertions inside - I couldn't find nicer way to do callbacks and still get errors when assertion fail


```json
"jest": {
"transformIgnorePatterns": ["node_modules/(?!(@react-native-community))/"]
Copy link

Choose a reason for hiding this comment

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

Suggested change
"transformIgnorePatterns": ["node_modules/(?!(@react-native-community))/"]
"transformIgnorePatterns": ["node_modules/(?!(react-native|@react-native-community))"]

@krizzu krizzu merged commit 485eba4 into master Apr 8, 2019
@krizzu krizzu deleted the jest-mocks branch April 8, 2019 06:08
@krizzu
Copy link
Member Author

krizzu commented Apr 10, 2019

🎉 This PR is included in version 1.3.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants