-
Notifications
You must be signed in to change notification settings - Fork 476
[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
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. | ||
|
||
## Using Async Storage mock | ||
|
||
Select a method that suits your needs: | ||
|
||
### Mock `node_modules` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Change to |
||
|
||
1. In your project root directory, create `__mocks__/@react-native-community` directory. | ||
2. Inside that folder, create `async-storage.js` file. | ||
3. Inside that file, export `Async Storage` mock. | ||
|
||
```javascript | ||
export default from '@react-native-community/async-storage/jest/async-storage-mock' | ||
``` | ||
|
||
### Use Jest setup files | ||
|
||
1. In your Jest config (probably in `package.json`) add setup files location: | ||
|
||
```json | ||
"jest": { | ||
"setupFiles": ["./path/to/jestSetupFile.js"] | ||
} | ||
``` | ||
|
||
2. Inside your setup file, set up Async Storage mocking: | ||
|
||
```javascript | ||
import mockAsyncStorage from '@react-native-community/async-storage/jest/async-storage-mock'; | ||
|
||
jest.mock('@react-native-community/async-storage', () => mockAsyncStorage); | ||
``` | ||
## Testing with mock | ||
|
||
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 () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
await asyncOperationOnAsyncStorage(); | ||
|
||
expect(AsyncStorage.getItem).toBeCalledWith('myKey'); | ||
}) | ||
``` | ||
|
||
## Overriding Mock logic | ||
|
||
You can override Async Storage mock implementation, by replacing its inner functions: | ||
|
||
```javascript | ||
// somewhere in your configuration files | ||
import AsyncStorageMock from '@react-native-community/async-storage/jest/async-storage-mock'; | ||
|
||
AsyncStorageMock.multiGet = jest.fn(([keys], callback) => { | ||
// do something here to retrieve files | ||
callback([]); | ||
}) | ||
|
||
export default AsyncStorageMock; | ||
``` | ||
|
||
You can [check mock implementation](../jest/async-storage-mock.js) to get more insight into its signatures. | ||
|
||
## Troubleshooting | ||
|
||
### **`SyntaxError: Unexpected token export` in async-storage/lib/index.js** | ||
|
||
This is likely because `Jest` is not transforming Async Storage. You can point it to do so, by adding `transformIgnorePatterns` setting in Jest's configuration. | ||
|
||
|
||
```json | ||
"jest": { | ||
"transformIgnorePatterns": ["/node_modules/@react-native-community/async-storage/(?!(lib))"] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see this addressed, mind fixing? |
||
} | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,157 @@ | ||
/** | ||
* @format | ||
* @lint-ignore-every XPLATJSCOPYRIGHT1 | ||
*/ | ||
/* eslint-disable no-shadow */ | ||
|
||
import 'react-native'; | ||
import React from 'react'; | ||
import App from '../App'; | ||
|
||
// Note: test renderer must be required after react-native. | ||
import renderer from 'react-test-renderer'; | ||
import AsyncStorage from '@react-native-community/async-storage'; | ||
|
||
it('renders correctly', () => { | ||
renderer.create(<App />); | ||
describe('Async Storage mock functionality', () => { | ||
describe('Promise based', () => { | ||
it('can read/write data to/from storage', async () => { | ||
const newData = Math.floor(Math.random() * 1000); | ||
|
||
await AsyncStorage.setItem('key', newData); | ||
|
||
const data = await AsyncStorage.getItem('key'); | ||
|
||
expect(data).toBe(newData); | ||
}); | ||
|
||
it('can clear storage', async () => { | ||
await AsyncStorage.setItem('temp_key', Math.random() * 1000); | ||
|
||
let currentValue = await AsyncStorage.getItem('temp_key'); | ||
|
||
expect(currentValue).not.toBeNull(); | ||
|
||
await AsyncStorage.clear(); | ||
|
||
currentValue = await AsyncStorage.getItem('temp_key'); | ||
|
||
expect(currentValue).toBeNull(); | ||
}); | ||
|
||
it('can clear entries in storage', async () => { | ||
await AsyncStorage.setItem('random1', Math.random() * 1000); | ||
await AsyncStorage.setItem('random2', Math.random() * 1000); | ||
|
||
let data1 = await AsyncStorage.getItem('random1'); | ||
let data2 = await AsyncStorage.getItem('random2'); | ||
|
||
expect(data1).not.toBeNull(); | ||
expect(data2).not.toBeNull(); | ||
|
||
await AsyncStorage.removeItem('random1'); | ||
await AsyncStorage.removeItem('random2'); | ||
data1 = await AsyncStorage.getItem('random1'); | ||
data2 = await AsyncStorage.getItem('random2'); | ||
expect(data2).toBeNull(); | ||
expect(data1).toBeNull(); | ||
}); | ||
|
||
it('can use merge with current data in storage', async () => { | ||
let originalPerson = { | ||
name: 'Jerry', | ||
age: 21, | ||
characteristics: { | ||
hair: 'black', | ||
eyes: 'green', | ||
}, | ||
}; | ||
|
||
await AsyncStorage.setItem('person', JSON.stringify(originalPerson)); | ||
|
||
originalPerson.name = 'Harry'; | ||
originalPerson.characteristics.hair = 'red'; | ||
originalPerson.characteristics.shoeSize = 40; | ||
|
||
await AsyncStorage.mergeItem('person', JSON.stringify(originalPerson)); | ||
|
||
const currentPerson = await AsyncStorage.getItem('person'); | ||
const person = JSON.parse(currentPerson); | ||
|
||
expect(person).toHaveProperty('name', 'Harry'); | ||
expect(person.characteristics).toHaveProperty('hair', 'red'); | ||
expect(person.characteristics).toHaveProperty('shoeSize', 40); | ||
}); | ||
}); | ||
|
||
describe('Callback based', () => { | ||
it('can read/write data to/from storage', done => { | ||
const newData = Math.floor(Math.random() * 1000); | ||
|
||
AsyncStorage.setItem('key', newData, function() { | ||
AsyncStorage.getItem('key', function(_, value) { | ||
expect(value).toBe(newData); | ||
done(); | ||
}).catch(e => done.fail(e)); | ||
}); | ||
}); | ||
it('can clear storage', done => { | ||
AsyncStorage.setItem('temp_key', Math.random() * 1000, () => { | ||
AsyncStorage.getItem('temp_key', (_, currentValue) => { | ||
expect(currentValue).not.toBeNull(); | ||
AsyncStorage.clear(() => { | ||
AsyncStorage.getItem('temp_key', (_, value) => { | ||
expect(value).toBeNull(); | ||
done(); | ||
}).catch(e => done.fail(e)); | ||
}); | ||
}).catch(e => done.fail(e)); | ||
}); | ||
}); | ||
|
||
it('can clear entries in storage', done => { | ||
AsyncStorage.setItem('random1', Math.random() * 1000, () => { | ||
AsyncStorage.setItem('random2', Math.random() * 1000, () => { | ||
AsyncStorage.getItem('random1', (_, data1) => { | ||
AsyncStorage.getItem('random2', (_, data2) => { | ||
expect(data1).not.toBeNull(); | ||
expect(data2).not.toBeNull(); | ||
|
||
AsyncStorage.removeItem('random1', () => { | ||
AsyncStorage.removeItem('random2', () => { | ||
AsyncStorage.getItem('random1', (_, value1) => { | ||
AsyncStorage.getItem('random2', (_, value2) => { | ||
expect(value1).toBeNull(); | ||
expect(value2).toBeNull(); | ||
done(); | ||
}).catch(e => done.fail(e)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you're using callback approach, but here taking advantage of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The reason why I'm catching some of the block is that Note that I only wrap blocks with assertions inside - I couldn't find nicer way to do |
||
}); | ||
}); | ||
}); | ||
}).catch(e => done.fail(e)); | ||
}); | ||
}); | ||
}); | ||
}); | ||
|
||
it('can use merge with current data in storage', done => { | ||
let originalPerson = { | ||
name: 'Jerry', | ||
age: 21, | ||
characteristics: { | ||
hair: 'black', | ||
eyes: 'green', | ||
}, | ||
}; | ||
|
||
AsyncStorage.setItem('person', JSON.stringify(originalPerson), () => { | ||
originalPerson.name = 'Harry'; | ||
originalPerson.characteristics.hair = 'red'; | ||
originalPerson.characteristics.shoeSize = 40; | ||
AsyncStorage.mergeItem('person', JSON.stringify(originalPerson), () => { | ||
AsyncStorage.getItem('person', (_, currentPerson) => { | ||
const person = JSON.parse(currentPerson); | ||
expect(person).toHaveProperty('name', 'Harry'); | ||
expect(person.characteristics).toHaveProperty('hair', 'red'); | ||
expect(person.characteristics).toHaveProperty('shoeSize', 40); | ||
done(); | ||
}).catch(e => done.fail(e)); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
{ | ||
"setupFilesAfterEnv": ["./init.js"], | ||
"testEnvironment": "node" | ||
"testEnvironment": "node", | ||
"testMatch": [ "**/?(*.)+(e2e).[jt]s?(x)" ] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
/** | ||
* @format | ||
*/ | ||
|
||
import mockAsyncStorage from '../jest/async-storage-mock'; | ||
|
||
jest.mock('@react-native-community/async-storage', () => mockAsyncStorage); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Native Module
->NativeModule