如何用 Jest 测试异步存储?
How to test Async Storage with Jest?
我正在使用 React Native 构建应用程序。我想尽量减少与数据库通信的频率,因此我大量使用 AsyncStorage。不过,在 DB 和 AsyncStorage 之间的转换中存在很多错误空间。因此,我想通过 运行ning 自动化测试来确保 AsyncStorage 拥有我认为它拥有的数据。令人惊讶的是,我还没有找到任何关于如何在线执行此操作的信息。我自己尝试做这件事没有成功。
使用 Jest:
it("can read asyncstorage", () => {
return AsyncStorage.getItem('foo').then(foo => {
expect(foo).not.toBe("");
}); });
此方法因错误而失败:
TypeError: RCTAsyncStorage.multiGet is not a function
删除 return 会导致它立即 运行 而不等待值并且不正确地通过测试。
当我尝试使用 await 关键字对其进行测试时,我遇到了完全相同的错误:
it('can read asyncstorage', async () => {
this.foo = "";
await AsyncStorage.getItem('foo').then(foo => {
this.foo = foo;
});
expect(foo).not.toBe(""); });
关于如何针对 AsyncStorage 中的值成功 运行 断言有什么建议吗?我更愿意继续使用 Jest,但如果它只能通过一些替代测试库来完成,我对此持开放态度。
也许你可以尝试这样的事情:
mockStorage.js
export default class MockStorage {
constructor(cache = {}) {
this.storageCache = cache;
}
setItem = jest.fn((key, value) => {
return new Promise((resolve, reject) => {
return (typeof key !== 'string' || typeof value !== 'string')
? reject(new Error('key and value must be string'))
: resolve(this.storageCache[key] = value);
});
});
getItem = jest.fn((key) => {
return new Promise((resolve) => {
return this.storageCache.hasOwnProperty(key)
? resolve(this.storageCache[key])
: resolve(null);
});
});
removeItem = jest.fn((key) => {
return new Promise((resolve, reject) => {
return this.storageCache.hasOwnProperty(key)
? resolve(delete this.storageCache[key])
: reject('No such key!');
});
});
clear = jest.fn((key) => {
return new Promise((resolve, reject) => resolve(this.storageCache = {}));
});
getAllKeys = jest.fn((key) => {
return new Promise((resolve, reject) => resolve(Object.keys(this.storageCache)));
});
}
在你的测试文件中:
import MockStorage from './MockStorage';
const storageCache = {};
const AsyncStorage = new MockStorage(storageCache);
jest.setMock('AsyncStorage', AsyncStorage)
// ... do things
我原来的回答只是指出了 react-native-simple-store 的作者是如何处理 mocking 的。我用自己的模拟更新了我的答案,删除了 Jason 的硬编码模拟响应。
Jason Merino has a nice simple approach to this in https://github.com/jasonmerino/react-native-simple-store/blob/master/tests/index-test.js#L31-L64
jest.mock('react-native', () => ({
AsyncStorage: {
setItem: jest.fn(() => {
return new Promise((resolve, reject) => {
resolve(null);
});
}),
multiSet: jest.fn(() => {
return new Promise((resolve, reject) => {
resolve(null);
});
}),
getItem: jest.fn(() => {
return new Promise((resolve, reject) => {
resolve(JSON.stringify(getTestData()));
});
}),
multiGet: jest.fn(() => {
return new Promise((resolve, reject) => {
resolve(multiGetTestData());
});
}),
removeItem: jest.fn(() => {
return new Promise((resolve, reject) => {
resolve(null);
});
}),
getAllKeys: jest.fn(() => {
return new Promise((resolve) => {
resolve(['one', 'two', 'three']);
});
})
}
}));
我自己的模拟:
const items = {};
jest.mock('react-native', () => ({
AsyncStorage: {
setItem: jest.fn((item, value) => {
return new Promise((resolve, reject) => {
items[item] = value;
resolve(value);
});
}),
multiSet: jest.fn((item, value) => {
return new Promise((resolve, reject) => {
items[item] = value;
resolve(value);
});
}),
getItem: jest.fn((item, value) => {
return new Promise((resolve, reject) => {
resolve(items[item]);
});
}),
multiGet: jest.fn((item) => {
return new Promise((resolve, reject) => {
resolve(items[item]);
});
}),
removeItem: jest.fn((item) => {
return new Promise((resolve, reject) => {
resolve(delete items[item]);
});
}),
getAllKeys: jest.fn((items) => {
return new Promise((resolve) => {
resolve(items.keys());
});
})
}
}));
我认为 jest.setMock 在这种情况下可能比 jest.mock 更好,所以我们可以毫无问题地使用 react-native
只是像这样模拟 AsyncStorage
:
jest.setMock('AsyncStorage', {
getItem: jest.fn(
item =>
new Promise((resolve, reject) => {
resolve({ myMockObjectToReturn: 'myMockObjectToReturn' });
})
),
});
对于 2019 年以后看到此问题的所有人:
自 Nov 2020, AsyncStorage was renamed back to @react-native-async-storage/async-storage" 以来,如果您从 react-native
:
导入它,则会导致出现此警告
Warning: Async Storage has been extracted from react-native core and will be removed in a future release.
新模块包含自己的 mock,因此您不必再为编写自己的 mock 而烦恼。
Per the project's documentation,您可以通过两种不同的方式进行设置:
##With mocks 目录
- 在您的项目根目录中,创建一个
__mocks__/@react-native-community
目录。
- 在该文件夹中,创建异步-storage.js 文件。
- 在该文件中,导出异步存储模拟。
export default from '@react-native-async-storage/async-storage/jest/async-storage-mock'
Jest 应该在所有测试中默认模拟 AsyncStorage
。如果没有,请尝试在测试文件的顶部调用 jest.mock(@react-native-async-storage/async-storage)
。
使用 Jest 安装文件
- 在您的 Jest 配置中(可能在
package.json
或 jest.config.js
中)添加安装文件的位置:
"jest": {
"setupFiles": ["./path/to/jestSetupFile.js"]
}
- 在您的设置文件中,设置 AsyncStorage 模拟:
import mockAsyncStorage from '@react-native-async-storage/async-storage/jest/async-storage-mock';
jest.mock('@react-native-community/async-storage', () => mockAsyncStorage);
如果您使用的是 TypeScript,使用第二个选项(Jest 安装文件)要容易得多,因为第一个选项(mocks 目录)不会关联 @types/react-native-community__async-storage
自动模拟。
使用命令安装模块:
运行这个命令来自项目的根目录。
npm install --save mock-async-storage
在项目根目录中创建__mocks__\@react-native-community
文件夹。在其中创建一个文件 async-storage.js。
async-storage.js
中的代码
export default from '@react-native-community/async-storage/jest/async-storage-mock'
里面package.json配置jest如下:
"jest": {
"preset": "jest-expo",
"transformIgnorePatterns" : ["/node_modules/@react-native-community/async-storage/(?!(lib))"]
},
这里我使用jest-expo进行测试。如果您使用的是 jest,那么预设值将是 jest 而不是 jest-expo.
在项目根目录中创建一个名为 jest.config.js 的文件
jest.config.js 文件中的配置:
module.exports = {
setupFilesAfterEnv: [
'./setup-tests.js',
],
};
在项目根目录中创建一个名为setup-tests.js 的文件。此文件中的代码是:
import MockAsyncStorage from 'mock-async-storage';
const mockImpl = new MockAsyncStorage();
jest.mock('@react-native-community/async-storage', () => mockImpl);
在项目根目录下创建测试文件。这里我称它为Example.test.js.
import AsyncStorage from '@react-native-community/async-storage';
beforeEach(() => {
AsyncStorage.clear();
// console.log(`After the data is being reset :`)
// console.log(AsyncStorage)
});
it('can read asyncstorage', async () => {
await AsyncStorage.setItem('username', 'testUser')
let usernameValue = await AsyncStorage.getItem('username')
// console.log(`After the data is being set :`)
// console.log(AsyncStorage)
expect(usernameValue).toBe('testUser')
});
此处使用 AsyncStorage.setItem 将 username 的值设置为 testUser。然后使用 getItem 函数获取值。
测试用例是比较usernameValue是否等于testUser。
如果是,则测试用例通过,否则测试用例将失败。
使用 beforeEach 以便每次测试用例被 运行 异步存储被清除并且为空。
如果需要,可以使用 console.log
检查 Asyncstorage 中存在的内容
运行 命令 yarn test 到 运行 测试。输出是:
我刚刚 运行 解决了这个问题 运行 与最新版本的 Expo 开玩笑,并按照 https://react-native-async-storage.github.io/async-storage/docs/advanced/jest/.[=11] 中的 'jest setup file' 选项解决了这个问题=]
请注意,AsyncStorage(无论出于何种原因)renamed\moved 变回“@react-native-async-storage/async-storage”并且其包名称中不再包含“community”。
1- 在根项目中添加文件夹 __mocks__
2- 在 __mocks__
文件夹
中添加文件夹 @react-native-async-storage
3- 在 @react-native-async-storage
中添加文件 async-storage.js
4- 添加代码:
let db = {};
export default {
setItem: (item, value) => {
return new Promise((resolve, reject) => {
db[item] = value;
resolve(value);
});
},
multiSet: (item, fun) => {
return new Promise((resolve, reject) => {
for (let index = 0; index < item.length; index++) {
db[item[index][0]] = item[index][1];
}
fun()
resolve(value);
});
},
getItem: (item, value= null) => {
return new Promise((resolve, reject) => {
resolve(db[item]);
});
},
multiGet: (item) => {
return new Promise((resolve, reject) => {
resolve(db[item]);
});
},
removeItem: (item) => {
return new Promise((resolve, reject) => {
resolve(delete db[item]);
});
},
getAllKeys: (db) => {
return new Promise((resolve) => {
resolve(db.keys());
});
}
}
我正在使用 React Native 构建应用程序。我想尽量减少与数据库通信的频率,因此我大量使用 AsyncStorage。不过,在 DB 和 AsyncStorage 之间的转换中存在很多错误空间。因此,我想通过 运行ning 自动化测试来确保 AsyncStorage 拥有我认为它拥有的数据。令人惊讶的是,我还没有找到任何关于如何在线执行此操作的信息。我自己尝试做这件事没有成功。
使用 Jest:
it("can read asyncstorage", () => {
return AsyncStorage.getItem('foo').then(foo => {
expect(foo).not.toBe("");
}); });
此方法因错误而失败:
TypeError: RCTAsyncStorage.multiGet is not a function
删除 return 会导致它立即 运行 而不等待值并且不正确地通过测试。
当我尝试使用 await 关键字对其进行测试时,我遇到了完全相同的错误:
it('can read asyncstorage', async () => {
this.foo = "";
await AsyncStorage.getItem('foo').then(foo => {
this.foo = foo;
});
expect(foo).not.toBe(""); });
关于如何针对 AsyncStorage 中的值成功 运行 断言有什么建议吗?我更愿意继续使用 Jest,但如果它只能通过一些替代测试库来完成,我对此持开放态度。
也许你可以尝试这样的事情:
mockStorage.js
export default class MockStorage {
constructor(cache = {}) {
this.storageCache = cache;
}
setItem = jest.fn((key, value) => {
return new Promise((resolve, reject) => {
return (typeof key !== 'string' || typeof value !== 'string')
? reject(new Error('key and value must be string'))
: resolve(this.storageCache[key] = value);
});
});
getItem = jest.fn((key) => {
return new Promise((resolve) => {
return this.storageCache.hasOwnProperty(key)
? resolve(this.storageCache[key])
: resolve(null);
});
});
removeItem = jest.fn((key) => {
return new Promise((resolve, reject) => {
return this.storageCache.hasOwnProperty(key)
? resolve(delete this.storageCache[key])
: reject('No such key!');
});
});
clear = jest.fn((key) => {
return new Promise((resolve, reject) => resolve(this.storageCache = {}));
});
getAllKeys = jest.fn((key) => {
return new Promise((resolve, reject) => resolve(Object.keys(this.storageCache)));
});
}
在你的测试文件中:
import MockStorage from './MockStorage';
const storageCache = {};
const AsyncStorage = new MockStorage(storageCache);
jest.setMock('AsyncStorage', AsyncStorage)
// ... do things
我原来的回答只是指出了 react-native-simple-store 的作者是如何处理 mocking 的。我用自己的模拟更新了我的答案,删除了 Jason 的硬编码模拟响应。
Jason Merino has a nice simple approach to this in https://github.com/jasonmerino/react-native-simple-store/blob/master/tests/index-test.js#L31-L64
jest.mock('react-native', () => ({
AsyncStorage: {
setItem: jest.fn(() => {
return new Promise((resolve, reject) => {
resolve(null);
});
}),
multiSet: jest.fn(() => {
return new Promise((resolve, reject) => {
resolve(null);
});
}),
getItem: jest.fn(() => {
return new Promise((resolve, reject) => {
resolve(JSON.stringify(getTestData()));
});
}),
multiGet: jest.fn(() => {
return new Promise((resolve, reject) => {
resolve(multiGetTestData());
});
}),
removeItem: jest.fn(() => {
return new Promise((resolve, reject) => {
resolve(null);
});
}),
getAllKeys: jest.fn(() => {
return new Promise((resolve) => {
resolve(['one', 'two', 'three']);
});
})
}
}));
我自己的模拟:
const items = {};
jest.mock('react-native', () => ({
AsyncStorage: {
setItem: jest.fn((item, value) => {
return new Promise((resolve, reject) => {
items[item] = value;
resolve(value);
});
}),
multiSet: jest.fn((item, value) => {
return new Promise((resolve, reject) => {
items[item] = value;
resolve(value);
});
}),
getItem: jest.fn((item, value) => {
return new Promise((resolve, reject) => {
resolve(items[item]);
});
}),
multiGet: jest.fn((item) => {
return new Promise((resolve, reject) => {
resolve(items[item]);
});
}),
removeItem: jest.fn((item) => {
return new Promise((resolve, reject) => {
resolve(delete items[item]);
});
}),
getAllKeys: jest.fn((items) => {
return new Promise((resolve) => {
resolve(items.keys());
});
})
}
}));
我认为 jest.setMock 在这种情况下可能比 jest.mock 更好,所以我们可以毫无问题地使用 react-native
只是像这样模拟 AsyncStorage
:
jest.setMock('AsyncStorage', {
getItem: jest.fn(
item =>
new Promise((resolve, reject) => {
resolve({ myMockObjectToReturn: 'myMockObjectToReturn' });
})
),
});
对于 2019 年以后看到此问题的所有人:
自 Nov 2020, AsyncStorage was renamed back to @react-native-async-storage/async-storage" 以来,如果您从 react-native
:
Warning: Async Storage has been extracted from react-native core and will be removed in a future release.
新模块包含自己的 mock,因此您不必再为编写自己的 mock 而烦恼。
Per the project's documentation,您可以通过两种不同的方式进行设置:
##With mocks 目录
- 在您的项目根目录中,创建一个
__mocks__/@react-native-community
目录。 - 在该文件夹中,创建异步-storage.js 文件。
- 在该文件中,导出异步存储模拟。
export default from '@react-native-async-storage/async-storage/jest/async-storage-mock'
Jest 应该在所有测试中默认模拟 AsyncStorage
。如果没有,请尝试在测试文件的顶部调用 jest.mock(@react-native-async-storage/async-storage)
。
使用 Jest 安装文件
- 在您的 Jest 配置中(可能在
package.json
或jest.config.js
中)添加安装文件的位置:"jest": { "setupFiles": ["./path/to/jestSetupFile.js"] }
- 在您的设置文件中,设置 AsyncStorage 模拟:
import mockAsyncStorage from '@react-native-async-storage/async-storage/jest/async-storage-mock'; jest.mock('@react-native-community/async-storage', () => mockAsyncStorage);
如果您使用的是 TypeScript,使用第二个选项(Jest 安装文件)要容易得多,因为第一个选项(mocks 目录)不会关联 @types/react-native-community__async-storage
自动模拟。
使用命令安装模块: 运行这个命令来自项目的根目录。
npm install --save mock-async-storage
在项目根目录中创建__mocks__\@react-native-community
文件夹。在其中创建一个文件 async-storage.js。
async-storage.js
export default from '@react-native-community/async-storage/jest/async-storage-mock'
里面package.json配置jest如下:
"jest": {
"preset": "jest-expo",
"transformIgnorePatterns" : ["/node_modules/@react-native-community/async-storage/(?!(lib))"]
},
这里我使用jest-expo进行测试。如果您使用的是 jest,那么预设值将是 jest 而不是 jest-expo.
在项目根目录中创建一个名为 jest.config.js 的文件 jest.config.js 文件中的配置:
module.exports = {
setupFilesAfterEnv: [
'./setup-tests.js',
],
};
在项目根目录中创建一个名为setup-tests.js 的文件。此文件中的代码是:
import MockAsyncStorage from 'mock-async-storage';
const mockImpl = new MockAsyncStorage();
jest.mock('@react-native-community/async-storage', () => mockImpl);
在项目根目录下创建测试文件。这里我称它为Example.test.js.
import AsyncStorage from '@react-native-community/async-storage';
beforeEach(() => {
AsyncStorage.clear();
// console.log(`After the data is being reset :`)
// console.log(AsyncStorage)
});
it('can read asyncstorage', async () => {
await AsyncStorage.setItem('username', 'testUser')
let usernameValue = await AsyncStorage.getItem('username')
// console.log(`After the data is being set :`)
// console.log(AsyncStorage)
expect(usernameValue).toBe('testUser')
});
此处使用 AsyncStorage.setItem 将 username 的值设置为 testUser。然后使用 getItem 函数获取值。 测试用例是比较usernameValue是否等于testUser。 如果是,则测试用例通过,否则测试用例将失败。
使用 beforeEach 以便每次测试用例被 运行 异步存储被清除并且为空。 如果需要,可以使用 console.log
检查 Asyncstorage 中存在的内容运行 命令 yarn test 到 运行 测试。输出是:
我刚刚 运行 解决了这个问题 运行 与最新版本的 Expo 开玩笑,并按照 https://react-native-async-storage.github.io/async-storage/docs/advanced/jest/.[=11] 中的 'jest setup file' 选项解决了这个问题=]
请注意,AsyncStorage(无论出于何种原因)renamed\moved 变回“@react-native-async-storage/async-storage”并且其包名称中不再包含“community”。
1- 在根项目中添加文件夹 __mocks__
2- 在 __mocks__
文件夹
@react-native-async-storage
3- 在 @react-native-async-storage
async-storage.js
4- 添加代码:
let db = {};
export default {
setItem: (item, value) => {
return new Promise((resolve, reject) => {
db[item] = value;
resolve(value);
});
},
multiSet: (item, fun) => {
return new Promise((resolve, reject) => {
for (let index = 0; index < item.length; index++) {
db[item[index][0]] = item[index][1];
}
fun()
resolve(value);
});
},
getItem: (item, value= null) => {
return new Promise((resolve, reject) => {
resolve(db[item]);
});
},
multiGet: (item) => {
return new Promise((resolve, reject) => {
resolve(db[item]);
});
},
removeItem: (item) => {
return new Promise((resolve, reject) => {
resolve(delete db[item]);
});
},
getAllKeys: (db) => {
return new Promise((resolve) => {
resolve(db.keys());
});
}
}