React Native屏幕阅读器状态下嵌入链接可点击性测试问题
React Native单元测试同时运行时无限挂起问题排查
我在React Native应用里实现了一个检测屏幕阅读器是否启用的函数:当屏幕阅读器开启时,Text组件内的嵌入超链接可通过屏幕阅读器触发点击,该功能在Android模拟器中测试正常。但编写测试用例时遇到问题:单独运行每个单元测试均可成功,但同时运行两个测试时会无限挂起。相关代码如下:
组件代码
export const ClickablePassage: React.FC = () => ( <> <Text accessibilityRole="link" accessibilityLabel="This is a link. Click here to go to the browser" onPress={() => onClickHyperlink('url')} > <Text > This is a link. </Text> <Text onPress={() => Linking.openURL('url')}> Click here </Text> <Text > to go to the browser </Text> </Text> </> )
业务函数代码
export const onClickHyperlink = async (url: string): Promise<any> => AccessibilityInfo.isScreenReaderEnabled().then((enabled) => { enabled && Linking.openURL('url') })
测试用例代码
describe('onClickHyperlink', () => { afterEach(() => { jest.resetAllMocks() }) it('calls a url when the screen reader is on', async () => { jest.spyOn(AccessibilityInfo, 'isScreenReaderEnabled').mockImplementation(() => Promise.resolve(true)) jest.spyOn(Linking, 'openURL').mockImplementation(() => Promise.resolve(true)) await onClickHyperlink('url') await expect(Linking.openURL).toHaveBeenCalledWith('url') }) it('does not call a url when the screen reader is off', async () => { jest.spyOn(AccessibilityInfo, 'isScreenReaderEnabled').mockImplementation(() => Promise.resolve(false)) jest.spyOn(Linking, 'openURL').mockImplementation(() => Promise.resolve(true)) await onClickHyperlink('url') await expect(Linking.openURL).not.toBeCalled() }) })
问题原因
核心问题是jest.resetAllMocks()无法完全恢复被jest.spyOn劫持的方法。这个方法只会重置mock的调用计数、实例等状态,但不会把被spy的方法还原成原始实现。当第二个测试运行时,前一个测试的mock残留状态会导致Promise处理异常,最终引发测试无限挂起。
解决方案
改用mockRestore()来单独恢复每个spy的原始实现,修改后的测试代码如下:
describe('onClickHyperlink', () => { let screenReaderSpy: jest.SpyInstance; let openURLSpy: jest.SpyInstance; afterEach(() => { // 恢复被spy的方法到原始实现 screenReaderSpy?.mockRestore(); openURLSpy?.mockRestore(); }) it('屏幕阅读器开启时调用指定链接', async () => { screenReaderSpy = jest.spyOn(AccessibilityInfo, 'isScreenReaderEnabled').mockResolvedValue(true); openURLSpy = jest.spyOn(Linking, 'openURL').mockResolvedValue(true); await onClickHyperlink('test-url'); // 这类断言无需await,同步执行即可 expect(Linking.openURL).toHaveBeenCalledWith('test-url'); }) it('屏幕阅读器关闭时不调用链接', async () => { screenReaderSpy = jest.spyOn(AccessibilityInfo, 'isScreenReaderEnabled').mockResolvedValue(false); openURLSpy = jest.spyOn(Linking, 'openURL').mockResolvedValue(true); await onClickHyperlink('test-url'); expect(Linking.openURL).not.toBeCalled(); }) })
额外优化点
onClickHyperlink函数中Linking.openURL('url')是硬编码字符串,应改为使用传入的参数url,否则无论传入什么参数都会打开固定链接:
export const onClickHyperlink = async (url: string): Promise<any> => AccessibilityInfo.isScreenReaderEnabled().then((enabled) => { enabled && Linking.openURL(url) })
- 测试中的
await expect(...)无需加await,因为toHaveBeenCalled、not.toBeCalled这类断言是同步执行的,不需要等待Promise。
内容的提问来源于stack exchange,提问作者MSG-404
相关产品推荐
相关产品推荐

