React Native Keychain 的 Jest 单元测试指南
前言
在 React Native 开发中,react-native-keychain
是一个常用的安全存储解决方案,它提供了安全的密码和凭证存储功能。然而,在进行单元测试时,由于 Jest 测试环境中没有原生模块的支持,直接使用该库会遇到问题。本文将详细介绍如何为 react-native-keychain
创建 Jest 模拟(mock),以便顺利进行单元测试。
为什么需要模拟 Keychain 模块
react-native-keychain
的核心功能依赖于原生平台的 Keychain/Keystore 服务,这些功能在纯 JavaScript 的 Jest 测试环境中是无法正常工作的。因此,我们需要创建模拟对象来替代真实的模块,这样可以在测试中:
- 避免调用实际的原生代码
- 控制函数的返回值以测试各种场景
- 验证函数是否被正确调用
创建 Keychain 模拟对象
首先,我们需要创建一个完整的模拟对象,它应该包含 react-native-keychain
模块的所有公共 API。以下是一个完整的模拟实现:
// keychainMock.ts
const keychainMock = {
// 安全级别枚举
SECURITY_LEVEL: {
SECURE_SOFTWARE: 'MOCK_SECURITY_LEVEL_SECURE_SOFTWARE',
SECURE_HARDWARE: 'MOCK_SECURITY_LEVEL_SECURE_HARDWARE',
ANY: 'MOCK_SECURITY_LEVEL_ANY',
},
// 可访问性枚举
ACCESSIBLE: {
WHEN_UNLOCKED: 'MOCK_AccessibleWhenUnlocked',
AFTER_FIRST_UNLOCK: 'MOCK_AccessibleAfterFirstUnlock',
ALWAYS: 'MOCK_AccessibleAlways',
WHEN_PASSCODE_SET_THIS_DEVICE_ONLY: 'MOCK_AccessibleWhenPasscodeSetThisDeviceOnly',
WHEN_UNLOCKED_THIS_DEVICE_ONLY: 'MOCK_AccessibleWhenUnlockedThisDeviceOnly',
AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY: 'MOCK_AccessibleAfterFirstUnlockThisDeviceOnly',
},
// 访问控制枚举
ACCESS_CONTROL: {
USER_PRESENCE: 'MOCK_UserPresence',
BIOMETRY_ANY: 'MOCK_BiometryAny',
BIOMETRY_CURRENT_SET: 'MOCK_BiometryCurrentSet',
DEVICE_PASSCODE: 'MOCK_DevicePasscode',
APPLICATION_PASSWORD: 'MOCK_ApplicationPassword',
BIOMETRY_ANY_OR_DEVICE_PASSCODE: 'MOCK_BiometryAnyOrDevicePasscode',
BIOMETRY_CURRENT_SET_OR_DEVICE_PASSCODE: 'MOCK_BiometryCurrentSetOrDevicePasscode',
},
// 认证类型枚举
AUTHENTICATION_TYPE: {
DEVICE_PASSCODE_OR_BIOMETRICS: 'MOCK_AuthenticationWithBiometricsDevicePasscode',
BIOMETRICS: 'MOCK_AuthenticationWithBiometrics',
},
// 存储类型枚举
STORAGE_TYPE: {
FB: 'MOCK_FacebookConceal',
AES: 'MOCK_KeystoreAESCBC',
RSA: 'MOCK_KeystoreRSAECB',
KC: 'MOCK_keychain',
},
// 核心方法模拟
setGenericPassword: jest.fn().mockResolvedValue({
service: 'mockService',
storage: 'mockStorage',
}),
getGenericPassword: jest.fn().mockResolvedValue({
username: 'mockUser',
password: 'mockPassword',
service: 'mockService',
storage: 'mockStorage',
}),
resetGenericPassword: jest.fn().mockResolvedValue(true),
hasGenericPassword: jest.fn().mockResolvedValue(true),
getAllGenericPasswordServices: jest
.fn()
.mockResolvedValue(['mockService1', 'mockService2']),
setInternetCredentials: jest.fn().mockResolvedValue({
service: 'mockService',
storage: 'mockStorage',
}),
getInternetCredentials: jest.fn().mockResolvedValue({
username: 'mockUser',
password: 'mockPassword',
service: 'mockService',
storage: 'mockStorage',
}),
resetInternetCredentials: jest.fn().mockResolvedValue(),
getSupportedBiometryType: jest.fn().mockResolvedValue('MOCK_TouchID'),
canImplyAuthentication: jest.fn().mockResolvedValue(true),
getSecurityLevel: jest.fn().mockResolvedValue('MOCK_SECURE_SOFTWARE'),
};
export default keychainMock;
两种模拟配置方法
方法一:使用 __mocks__
目录
这是 Jest 推荐的模块模拟方式,适用于全局模拟整个模块:
- 在项目根目录创建
__mocks__
目录 - 在
__mocks__
目录下创建react-native-keychain
子目录 - 在该子目录中创建
index.js
或index.ts
文件
文件内容如下:
// __mocks__/react-native-keychain/index.js
const keychainMock = {
// 这里放置完整的模拟对象内容
// 与上面创建的 keychainMock 相同
};
module.exports = keychainMock;
这种方法会自动应用于所有测试文件中对该模块的引用,无需在每个测试文件中单独设置。
方法二:使用 Jest 配置文件
如果你需要更灵活的模拟控制,可以通过 Jest 的配置文件来设置:
- 在
jest.config.js
中添加 setup 文件配置:
module.exports = {
// 其他配置...
setupFiles: ['<rootDir>/jest.setup.js'],
};
- 创建
jest.setup.js
文件并添加以下内容:
// jest.setup.js
import keychainMock from './path/to/keychainMock';
jest.mock('react-native-keychain', () => keychainMock);
这种方法允许你在一个集中位置管理所有模拟设置,适合大型项目。
编写测试用例
有了模拟设置后,我们可以编写测试用例来验证使用了 react-native-keychain
的代码。以下是一些典型测试示例:
import Keychain from 'react-native-keychain';
describe('Keychain 功能测试', () => {
beforeEach(() => {
// 清除所有模拟函数的调用记录
jest.clearAllMocks();
});
it('应该能保存和获取通用密码', async () => {
// 测试保存密码
await Keychain.setGenericPassword('testUser', 'testPassword');
expect(Keychain.setGenericPassword).toHaveBeenCalledWith(
'testUser',
'testPassword'
);
// 测试获取密码
const credentials = await Keychain.getGenericPassword();
expect(credentials).toEqual({
username: 'mockUser',
password: 'mockPassword',
service: 'mockService',
storage: 'mockStorage',
});
});
it('应该能检查密码是否存在', async () => {
const exists = await Keychain.hasGenericPassword();
expect(exists).toBe(true);
});
it('应该能重置密码', async () => {
const reset = await Keychain.resetGenericPassword();
expect(reset).toBe(true);
});
it('应该能获取支持的生物识别类型', async () => {
const biometryType = await Keychain.getSupportedBiometryType();
expect(biometryType).toBe('MOCK_TouchID');
});
});
高级测试技巧
模拟不同的返回结果
你可以在特定测试中修改模拟函数的返回值:
it('应该处理获取密码失败的情况', async () => {
// 修改模拟行为
(Keychain.getGenericPassword as jest.Mock).mockResolvedValueOnce(false);
const result = await Keychain.getGenericPassword();
expect(result).toBe(false);
});
验证函数调用参数
it('应该使用正确的参数设置网络凭证', async () => {
const server = 'example.com';
const username = 'user';
const password = 'pass';
await Keychain.setInternetCredentials(server, username, password);
expect(Keychain.setInternetCredentials).toHaveBeenCalledWith(
server,
username,
password
);
});
测试错误处理
it('应该处理保存密码时的错误', async () => {
// 模拟抛出错误
(Keychain.setGenericPassword as jest.Mock).mockRejectedValueOnce(
new Error('保存失败')
);
await expect(Keychain.setGenericPassword('user', 'pass'))
.rejects
.toThrow('保存失败');
});
总结
通过本文介绍的方法,你可以轻松地为 react-native-keychain
创建 Jest 模拟,从而在单元测试中验证与安全存储相关的逻辑。记住:
- 完整的模拟应该包含所有枚举和方法
- 可以选择全局模拟或按需模拟
- 利用 Jest 的模拟功能可以测试各种场景
- 记得在测试之间清理模拟状态
良好的测试覆盖可以确保你的安全存储逻辑在各种情况下都能正常工作,而无需依赖实际的原生模块。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考