You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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();
  })
})

额外优化点

  1. onClickHyperlink函数中Linking.openURL('url')是硬编码字符串,应改为使用传入的参数url,否则无论传入什么参数都会打开固定链接:
export const onClickHyperlink = async (url: string): Promise<any> => 
  AccessibilityInfo.isScreenReaderEnabled().then((enabled) => { 
    enabled && Linking.openURL(url) 
  })
  1. 测试中的await expect(...)无需加await,因为toHaveBeenCalled、not.toBeCalled这类断言是同步执行的,不需要等待Promise。

内容的提问来源于stack exchange,提问作者MSG-404

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 10:48:13