如何使用 Jest 测试异步存储?
Posted
技术标签:
【中文标题】如何使用 Jest 测试异步存储?【英文标题】:How to test Async Storage with Jest? 【发布时间】:2017-04-18 13:34:54 【问题描述】:我正在使用 React Native 构建一个应用程序。我想尽量减少与数据库通信的频率,所以我大量使用 AsyncStorage。但是,在 DB 和 AsyncStorage 之间的转换中存在很大的错误空间。因此,我想通过对它运行自动化测试来确保 AsyncStorage 拥有我相信的数据。令人惊讶的是,我还没有找到任何有关如何在线执行此操作的信息。我自己尝试做这件事并没有成功。
使用 Jest:
it("can read asyncstorage", () =>
return AsyncStorage.getItem('foo').then(foo =>
expect(foo).not.toBe("");
); );
此方法失败并出现错误:
TypeError: RCTAsyncStorage.multiGet is not a function
删除 return 将导致它立即运行而无需等待 value 并错误地通过测试。
当我尝试使用 await 关键字对其进行测试时,遇到了完全相同的错误:
it('can read asyncstorage', async () =>
this.foo = "";
await AsyncStorage.getItem('foo').then(foo =>
this.foo = foo;
);
expect(foo).not.toBe(""); );
关于如何针对 AsyncStorage 中的值成功运行断言有什么建议吗?我更愿意继续使用 Jest,但如果它只能通过一些备用测试库来完成,我对此持开放态度。
【问题讨论】:
【参考方案1】: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());
);
【讨论】:
【参考方案2】:对于所有在 > 2019 年看到此问题的人:
自Nov 2020 起,AsyncStorage 被重命名回@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.
新模块包含自己的模拟,因此您不必再担心自己编写。
Per the project's documentation,您可以通过两种不同的方式进行设置:
##With 模拟目录
在您的项目根目录中,创建一个__mocks__/@react-native-community
目录。
在该文件夹中,创建 async-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 设置文件)要容易得多,因为第一个选项(模拟目录)不会将 @types/react-native-community__async-storage
与模拟关联自动。
【讨论】:
我在async-storage
文件夹中看不到任何/jest
文件夹:( 开玩笑抛出一个错误,它可以找到您指定的路径
@betoharres,你使用的是@react-native-community/async-storage
还是React Native内置的AsyncStorage
?
@david-catillo 是的!我就像他们落后的 1 个版本一样将模拟添加到库中。在此处参考我的解决方案:github.com/react-native-community/react-native-async-storage/…
注意:上面提到的包还不能用于expo项目:github.com/react-native-community/async-storage/issues/172
对于 Expo:这是一种在测试或 jest 设置文件中存根 AsyncStorage 的简单方法:jest.mock("@react-native-community/async-storage", () => ( AsyncStorage: clear: jest.fn().mockName("clear"), getAllKeys: jest.fn().mockName("getAllKeys"), getItem: jest.fn().mockName("getItem") ), removeItem: jest.fn().mockName("removeItem"), setItem: jest.fn().mockName("setItem") ));【参考方案3】:
我刚刚在最新版本的 Expo 中运行 jest 时遇到了这个问题,并按照 https://react-native-async-storage.github.io/async-storage/docs/advanced/jest/ 的说明中的“jest setup file”选项解决了这个问题。
请注意,AsyncStorage(无论出于何种原因)已重命名\移回“@react-native-async-storage/async-storage”,并且其包名称中不再包含“community”。
【讨论】:
【参考方案4】:使用以下命令安装模块: 从项目的根目录运行此命令。
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 以便每次运行测试用例时 Asyncstorage 都会被清除并且为空。 如果需要,可以使用 console.log 检查 Asyncstorage 中存在的内容
运行命令 yarn test 来运行测试。输出是:
【讨论】:
【参考方案5】:我认为 jest.setMock 在这种情况下可能比 jest.mock 更好,因此我们可以使用 react-native
毫无问题地像这样模拟 AsyncStorage
:
jest.setMock('AsyncStorage',
getItem: jest.fn(
item =>
new Promise((resolve, reject) =>
resolve( myMockObjectToReturn: 'myMockObjectToReturn' );
)
),
);
【讨论】:
【参考方案6】:也许你可以试试这样的:
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
【讨论】:
Getting 'Cannot find module 'AsyncStorage' from 'myFileName.js' ' ,尽管尝试从'react-native'导入它-有什么想法吗? :) 应该被接受的答案,因为它最清楚地回答了起源问题 如何清除storageCache
?我试过beforeEach
和await AsyncStorage.clear()
但它不影响模拟的storageCache
我使用这个答案作为解决方案,但我遇到了与@jhm 相同的问题。任何进展?谢谢。【参考方案7】:
我最初的回答只是指出 react-native-simple-store 的作者是如何处理嘲笑的。我用我自己的模拟更新了我的答案,删除了 Jason 的硬编码模拟响应。
Jason Merino 在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());
);
)
));
【讨论】:
此解决方案仅适用于您的用例,下面的解决方案应该是公认的解决方案! @Ouadie 它需要编辑,因此定义了multiGet
和 multiSet
。 @brian-case 专门询问 multiGet
。我会挖掘出我是如何嘲笑我的,避免对resolve()
的硬编码回复
Getting 'Cannot find module 'AsyncStorage' from 'react-native-implementation.js' 在我的测试中使用你的模拟 - 知道为什么吗? :) @jaygooby
@jaygooby,而不是items.keys()
应该是Object.keys(items)
因为const items =
:The module factory of 'jest.mock()' is not allowed to reference any out-of-scope variables. Invalid variable access: items
。以上是关于如何使用 Jest 测试异步存储?的主要内容,如果未能解决你的问题,请参考以下文章
使用 jest 和 avoriaz 时如何在 Vuejs 的组件中测试异步方法