如何使用 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.jsonjest.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 ?我试过beforeEachawait 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 它需要编辑,因此定义了 multiGetmultiSet。 @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 的组件中测试异步方法

如何使用 Jest 模拟异步函数

Vue with jest - 使用异步调用进行测试

如何使用 Jest 测试 Thunk 操作?

React Jest:如何模拟返回数据的异步 API 调用?

如何检查 Jest 中异步抛出的错误类型?