【问题标题】:How to test Async Storage with Jest?如何使用 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,但如果它只能通过一些备用测试库来完成,我对此持开放态度。

【问题讨论】:

    标签: reactjs asynchronous react-native jestjs asyncstorage


    【解决方案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 Merinohttps://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
            猜你喜欢
            • 2021-02-17
            • 2018-03-24
            • 2023-04-06
            • 1970-01-01
            • 2018-09-20
            • 2018-01-04
            • 1970-01-01
            • 2020-01-20
            • 1970-01-01
            相关资源
            最近更新 更多