Skip to content

Commit 8060f54

Browse files
author
Krzysztof Borowy
committed
feat: adds mock implementation, docs explaining
1 parent 22d4e5e commit 8060f54

File tree

8 files changed

+238
-11
lines changed

8 files changed

+238
-11
lines changed

docs/Jest-integration.md

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# Jest integration
2+
3+
In order to use Async Storage in your Jest tests, you need to [add implementation of its Native Module.](https://jestjs.io/docs/en/manual-mocks)
4+
This library comes with a *simple* mock (no actual functionality) that you can use. To add a custom behavior to it, checkout [customizing mock logic](#Custom-Mock-logic) section.
5+
6+
7+
## Using Async Storage mock
8+
9+
You can use mocked Async Storage in one of two ways:
10+
11+
### Mock `node_modules`
12+
13+
1. In your project root directory, create `__mocks__` directory.
14+
2. Inside that folder, create `react-native.js` file.
15+
3. Inside that file, import `AsyncStorageMock` and use it to mock `NativeModules`.
16+
17+
18+
```js
19+
20+
import AsyncStorageMock from '@react-native-community/async-storage/jest/async-storage-mock';
21+
22+
jest.doMock('react-native', () => {
23+
const reactNative = require.requireActual('react-native');
24+
25+
reactNative.NativeModules.RNCAsyncStorage = AsyncStorageMock;
26+
27+
return reactNative;
28+
});
29+
30+
// to contain all other exports from RN
31+
export * from 'react-native';
32+
33+
```
34+
35+
### Use Jest setup files
36+
37+
1. In Jest config (probably in `package.json`) add setup files location:
38+
39+
```json
40+
// ...
41+
"jest": {
42+
// other config
43+
"setupFiles": "./path/to/jestSetupFile.js"
44+
},
45+
// ...
46+
47+
```
48+
49+
2. Inside your setup file, set up Async Storage mocking:
50+
51+
52+
```js
53+
54+
import AsyncStorageMock from '@react-native-community/async-storage/jest/async-storage-mock';
55+
56+
jest.doMock('react-native', () => {
57+
const reactNative = require.requireActual('react-native');
58+
59+
reactNative.NativeModules.RNCAsyncStorage = AsyncStorageMock;
60+
61+
return reactNative;
62+
});
63+
64+
```
65+
66+
## Custom Mock logic
67+
68+
`AsyncStorageMock` is a simple mock, without implemented functionality. If your test case relies on a storage, you can add you custom logic to it. [Go over a sample implementation in example app](../example/__mocks__/react-native.js) or see the code below:
69+
70+
71+
```js
72+
// somewhere in your configuration files
73+
import AsyncStorageMock from '@react-native-community/async-storage/jest/async-storage-mock';
74+
75+
AsyncStorageMock.multiGet = jest.fn(([keys], callback) => {
76+
// call callback with "retrieved" value after .multiGet call
77+
callback();
78+
})
79+
80+
export default AsyncStorageMock;
81+
82+
```
83+
84+
You can [check mock itself](../jest/async-storage-mock.js) to see each method's signatures.
85+
86+
87+
## Troubleshooting
88+
89+
90+
1. **`SyntaxError: Unexpected token export` in async-storage/lib/index.js**
91+
92+
This is likely because `Jest` is not transforming Async Storage. You can point it to do so, by adding line in Jest's configuration:
93+
94+
```json
95+
"jest": {
96+
// ...
97+
"transformIgnorePatterns": ["/node_modules/@react-native-community/async-storage/(?!(lib))"]
98+
// ...
99+
}
100+
```

example/__mocks__/react-native.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/**
2+
* @format
3+
*/
4+
5+
import AsyncStorageMock from '../../jest/async-storage-mock';
6+
7+
jest.doMock('react-native', () => {
8+
const reactNative = require.requireActual('react-native');
9+
10+
let STORAGE = {};
11+
12+
// Implementing simple multiGet functionality
13+
AsyncStorageMock.multiGet = jest.fn((keys, cb) => {
14+
const values = keys.map(key => [key, STORAGE[key]]);
15+
16+
cb(null, values);
17+
});
18+
19+
// Implementing simple multiSet functionality
20+
AsyncStorageMock.multiSet = jest.fn((keyValuePairs, cb) => {
21+
keyValuePairs.forEach(keyValue => {
22+
const key = keyValue[0];
23+
const value = keyValue[1];
24+
25+
STORAGE[key] = value;
26+
});
27+
28+
cb();
29+
});
30+
31+
// Implementation of clear functionality
32+
AsyncStorageMock.clear = jest.fn(cb => {
33+
Object.keys(STORAGE).forEach(key => {
34+
delete STORAGE[key];
35+
});
36+
37+
cb();
38+
});
39+
40+
// use mock
41+
reactNative.NativeModules.RNCAsyncStorage = AsyncStorageMock;
42+
43+
return reactNative;
44+
});
45+
46+
export * from 'react-native';

example/__tests__/App.js

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,34 @@
11
/**
22
* @format
3-
* @lint-ignore-every XPLATJSCOPYRIGHT1
43
*/
54

5+
// "trigger" mocking
66
import 'react-native';
7-
import React from 'react';
8-
import App from '../App';
97

10-
// Note: test renderer must be required after react-native.
11-
import renderer from 'react-test-renderer';
8+
import AsyncStorage from '@react-native-community/async-storage';
129

13-
it('renders correctly', () => {
14-
renderer.create(<App />);
10+
describe('Example of AsyncStorage', () => {
11+
it('can read/write data to/from storage', async () => {
12+
const newData = Math.floor(Math.random() * 1000);
13+
14+
await AsyncStorage.setItem('key', newData);
15+
16+
const data = await AsyncStorage.getItem('key');
17+
18+
expect(data).toBe(newData);
19+
});
20+
21+
it('can clear storage', async () => {
22+
await AsyncStorage.setItem('temp_key', Math.random() * 1000);
23+
24+
let currentValue = await AsyncStorage.getItem('temp_key');
25+
26+
expect(currentValue).not.toBeNull();
27+
28+
await AsyncStorage.clear();
29+
30+
currentValue = await AsyncStorage.getItem('temp_key');
31+
32+
expect(currentValue).toBeNull();
33+
});
1534
});

example/e2e/config.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
22
"setupFilesAfterEnv": ["./init.js"],
3-
"testEnvironment": "node"
3+
"testEnvironment": "node",
4+
"testMatch": [ "**/?(*.)+(spec).[jt]s?(x)" ]
45
}

jest/async-storage-mock.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* @format
3+
* @flow
4+
*/
5+
6+
type KeyValueType = Array<[string, any]>;
7+
8+
type CallbackType = (error: ?Error) => void;
9+
10+
type KeysType = Array<string>;
11+
12+
type GetKeysType = (error: ?Error, keys: KeysType) => void;
13+
14+
type MultiGetType = (error: ?Error, result: KeyValueType) => void;
15+
16+
const AsyncStorageMock = {
17+
/**
18+
* Clears all keys in storage
19+
*/
20+
clear: (cb: CallbackType) => cb(),
21+
22+
/**
23+
* Returns an array of keys from storage
24+
*/
25+
getAllKeys: (cb: GetKeysType) => cb(null, []),
26+
27+
/**
28+
* Returns an array of array of key-value pair from storage
29+
* i.e: [["name", "John"], ["age", 23]]
30+
*/
31+
multiGet: (_: KeyValueType, cb: MultiGetType) => cb(null, []),
32+
33+
/**
34+
* Saves key-values to storage, by accepting array of array of key-values
35+
* i.e: [["name", "Sarah"], ["age", 32]]
36+
*/
37+
multiSet: (_: KeyValueType, cb: CallbackType) => cb(null),
38+
39+
/**
40+
* Removes entries with given keys from storage
41+
*/
42+
multiRemove: (_: KeysType, cb: CallbackType) => cb(),
43+
44+
/**
45+
* Merges existing and new values for a given set of keys.
46+
*/
47+
multiMerge: (_: KeyValueType, cb: CallbackType) => cb(),
48+
};
49+
50+
export default AsyncStorageMock;

lib/hooks.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
/**
2+
* @format
3+
*/
4+
15
import AsyncStorage from './AsyncStorage';
26

37
type AsyncStorageHook = {

lib/index.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,8 @@
1-
export default from './AsyncStorage';
2-
export { useAsyncStorage } from './hooks';
1+
/**
2+
* @format
3+
*/
4+
5+
import AsyncStorage from './AsyncStorage';
6+
7+
export default AsyncStorage;
8+
export {useAsyncStorage} from './hooks';

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@
5454
"semantic-release": "15.13.3"
5555
},
5656
"jest": {
57-
"preset": "react-native"
57+
"preset": "react-native",
58+
"testMatch": [ "**/__tests__/**/*.[jt]s?(x)", "**/?(*.)+(test).[jt]s?(x)" ]
5859
},
5960
"detox": {
6061
"test-runner": "jest",

0 commit comments

Comments
 (0)