Skip to content

Commit 485eba4

Browse files
author
Krzysztof Borowy
authored
feat: Jest mocks, testing guidelines
* added mock implementation * created docs for testing with JEst
1 parent 442c74b commit 485eba4

12 files changed

+403
-16
lines changed

.circleci/config.yml

+1-2
Original file line numberDiff line numberDiff line change
@@ -271,8 +271,7 @@ jobs:
271271
- run:
272272
name: Wake device
273273
command: |
274-
adb shell input keyevent
275-
adb shell input keyevent 82 &
274+
adb shell input keyevent 82
276275
277276
- run:
278277
name: Run e2e tests

README.md

+4
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ getData = async () => {
5656

5757
See docs for [api and more examples.](docs/API.md)
5858

59+
## Writing tests
60+
61+
Using [Jest](https://jestjs.io/) for testing? Make sure to check out [docs on how to integrate it with this module.](./docs/Jest-integration.md)
62+
5963
## Contribution
6064

6165
See the [CONTRIBUTING](CONTRIBUTING.md) file for how to help out.

docs/Jest-integration.md

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# Jest integration
2+
3+
Async Storage module is tighly coupled with its `NativeModule` part - 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 a mocked `Async Storage` module.
4+
5+
## Using Async Storage mock
6+
7+
You can use one of two ways to provide mocked version of `AsyncStorage`:
8+
9+
### With __mocks__ directory
10+
11+
1. In your project root directory, create `__mocks__/@react-native-community` directory.
12+
2. Inside that folder, create `async-storage.js` file.
13+
3. Inside that file, export `Async Storage` mock.
14+
15+
```javascript
16+
export default from '@react-native-community/async-storage/jest/async-storage-mock'
17+
```
18+
19+
### With Jest setup file
20+
21+
1. In your Jest config (probably in `package.json`) add setup files location:
22+
23+
```json
24+
"jest": {
25+
"setupFiles": ["./path/to/jestSetupFile.js"]
26+
}
27+
```
28+
29+
2. Inside your setup file, set up Async Storage mocking:
30+
31+
```javascript
32+
import mockAsyncStorage from '@react-native-community/async-storage/jest/async-storage-mock';
33+
34+
jest.mock('@react-native-community/async-storage', () => mockAsyncStorage);
35+
```
36+
## Testing with mock
37+
38+
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, if `.getItem` has been called with a specific arguments:
39+
40+
```javascript
41+
it('checks if Async Storage is used', async () => {
42+
await asyncOperationOnAsyncStorage();
43+
44+
expect(AsyncStorage.getItem).toBeCalledWith('myKey');
45+
})
46+
```
47+
48+
## Overriding Mock logic
49+
50+
You can override mock implementation, by replacing its inner functions:
51+
52+
```javascript
53+
// somewhere in your configuration files
54+
import AsyncStorageMock from '@react-native-community/async-storage/jest/async-storage-mock';
55+
56+
AsyncStorageMock.multiGet = jest.fn(([keys], callback) => {
57+
// do something here to retrieve data
58+
callback([]);
59+
})
60+
61+
export default AsyncStorageMock;
62+
```
63+
64+
You can [check its implementation](../jest/async-storage-mock.js) to get more insight into methods signatures.
65+
66+
## Troubleshooting
67+
68+
### **`SyntaxError: Unexpected token export` in async-storage/lib/index.js**
69+
70+
**Note:** In React Native 0.60+, all `@react-native-community` packages are transformed by default.
71+
72+
You need to point Jest to transform this package. You can do so, by adding Async Storage path to `transformIgnorePatterns` setting in Jest's configuration.
73+
74+
75+
```json
76+
"jest": {
77+
"transformIgnorePatterns": ["node_modules/(?!(@react-native-community/async-storage/lib))"]
78+
}
79+
```
80+
81+
Optionally, you can transform whole scope for `react-native-community` and `react-native`:
82+
83+
```json
84+
"jest": {
85+
"transformIgnorePatterns": ["node_modules/(?!(@react-native-community|react-native))"]
86+
}
87+
```

example/__tests__/App.js

+149-7
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,157 @@
11
/**
22
* @format
3-
* @lint-ignore-every XPLATJSCOPYRIGHT1
43
*/
4+
/* eslint-disable no-shadow */
55

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('Async Storage mock functionality', () => {
11+
describe('Promise based', () => {
12+
it('can read/write data to/from storage', async () => {
13+
const newData = Math.floor(Math.random() * 1000);
14+
15+
await AsyncStorage.setItem('key', newData);
16+
17+
const data = await AsyncStorage.getItem('key');
18+
19+
expect(data).toBe(newData);
20+
});
21+
22+
it('can clear storage', async () => {
23+
await AsyncStorage.setItem('temp_key', Math.random() * 1000);
24+
25+
let currentValue = await AsyncStorage.getItem('temp_key');
26+
27+
expect(currentValue).not.toBeNull();
28+
29+
await AsyncStorage.clear();
30+
31+
currentValue = await AsyncStorage.getItem('temp_key');
32+
33+
expect(currentValue).toBeNull();
34+
});
35+
36+
it('can clear entries in storage', async () => {
37+
await AsyncStorage.setItem('random1', Math.random() * 1000);
38+
await AsyncStorage.setItem('random2', Math.random() * 1000);
39+
40+
let data1 = await AsyncStorage.getItem('random1');
41+
let data2 = await AsyncStorage.getItem('random2');
42+
43+
expect(data1).not.toBeNull();
44+
expect(data2).not.toBeNull();
45+
46+
await AsyncStorage.removeItem('random1');
47+
await AsyncStorage.removeItem('random2');
48+
data1 = await AsyncStorage.getItem('random1');
49+
data2 = await AsyncStorage.getItem('random2');
50+
expect(data2).toBeNull();
51+
expect(data1).toBeNull();
52+
});
53+
54+
it('can use merge with current data in storage', async () => {
55+
let originalPerson = {
56+
name: 'Jerry',
57+
age: 21,
58+
characteristics: {
59+
hair: 'black',
60+
eyes: 'green',
61+
},
62+
};
63+
64+
await AsyncStorage.setItem('person', JSON.stringify(originalPerson));
65+
66+
originalPerson.name = 'Harry';
67+
originalPerson.characteristics.hair = 'red';
68+
originalPerson.characteristics.shoeSize = 40;
69+
70+
await AsyncStorage.mergeItem('person', JSON.stringify(originalPerson));
71+
72+
const currentPerson = await AsyncStorage.getItem('person');
73+
const person = JSON.parse(currentPerson);
74+
75+
expect(person).toHaveProperty('name', 'Harry');
76+
expect(person.characteristics).toHaveProperty('hair', 'red');
77+
expect(person.characteristics).toHaveProperty('shoeSize', 40);
78+
});
79+
});
80+
81+
describe('Callback based', () => {
82+
it('can read/write data to/from storage', done => {
83+
const newData = Math.floor(Math.random() * 1000);
84+
85+
AsyncStorage.setItem('key', newData, function() {
86+
AsyncStorage.getItem('key', function(_, value) {
87+
expect(value).toBe(newData);
88+
done();
89+
}).catch(e => done.fail(e));
90+
});
91+
});
92+
it('can clear storage', done => {
93+
AsyncStorage.setItem('temp_key', Math.random() * 1000, () => {
94+
AsyncStorage.getItem('temp_key', (_, currentValue) => {
95+
expect(currentValue).not.toBeNull();
96+
AsyncStorage.clear(() => {
97+
AsyncStorage.getItem('temp_key', (_, value) => {
98+
expect(value).toBeNull();
99+
done();
100+
}).catch(e => done.fail(e));
101+
});
102+
}).catch(e => done.fail(e));
103+
});
104+
});
105+
106+
it('can clear entries in storage', done => {
107+
AsyncStorage.setItem('random1', Math.random() * 1000, () => {
108+
AsyncStorage.setItem('random2', Math.random() * 1000, () => {
109+
AsyncStorage.getItem('random1', (_, data1) => {
110+
AsyncStorage.getItem('random2', (_, data2) => {
111+
expect(data1).not.toBeNull();
112+
expect(data2).not.toBeNull();
113+
114+
AsyncStorage.removeItem('random1', () => {
115+
AsyncStorage.removeItem('random2', () => {
116+
AsyncStorage.getItem('random1', (_, value1) => {
117+
AsyncStorage.getItem('random2', (_, value2) => {
118+
expect(value1).toBeNull();
119+
expect(value2).toBeNull();
120+
done();
121+
}).catch(e => done.fail(e));
122+
});
123+
});
124+
});
125+
}).catch(e => done.fail(e));
126+
});
127+
});
128+
});
129+
});
130+
131+
it('can use merge with current data in storage', done => {
132+
let originalPerson = {
133+
name: 'Jerry',
134+
age: 21,
135+
characteristics: {
136+
hair: 'black',
137+
eyes: 'green',
138+
},
139+
};
140+
141+
AsyncStorage.setItem('person', JSON.stringify(originalPerson), () => {
142+
originalPerson.name = 'Harry';
143+
originalPerson.characteristics.hair = 'red';
144+
originalPerson.characteristics.shoeSize = 40;
145+
AsyncStorage.mergeItem('person', JSON.stringify(originalPerson), () => {
146+
AsyncStorage.getItem('person', (_, currentPerson) => {
147+
const person = JSON.parse(currentPerson);
148+
expect(person).toHaveProperty('name', 'Harry');
149+
expect(person.characteristics).toHaveProperty('hair', 'red');
150+
expect(person.characteristics).toHaveProperty('shoeSize', 40);
151+
done();
152+
}).catch(e => done.fail(e));
153+
});
154+
});
155+
});
156+
});
15157
});
File renamed without changes.

example/e2e/config.json

+2-1
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": [ "**/?(*.)+(e2e).[jt]s?(x)" ]
45
}

example/jest.setup.js

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/**
2+
* @format
3+
*/
4+
5+
import mockAsyncStorage from '../jest/async-storage-mock';
6+
7+
jest.mock('@react-native-community/async-storage', () => mockAsyncStorage);

0 commit comments

Comments
 (0)