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

如何在无直接访问权限时获取命名ReceivePort的SendPort用于测试?

问题描述

我正在进行单元测试,尝试模拟一个通过FFI调用C函数的类。C中有一对负责启动和停止线程的函数:

  • 启动函数会接收nativePort,用于和线程通信,C端会全局存储该端口句柄并启动线程处理任务
  • 停止函数会发出信号让线程结束,随后线程会使用存储的nativePort句柄通知Dart侧的监听器任务已完成

具体流程:

  1. Dart侧的DartFunc1()通过ReceivePort.SendPort.nativePort将端口传递给C原生库
  2. C库全局存储端口句柄并启动线程处理任务
  3. Dart侧的DartFunc2()调用C原生停止函数,C端使用全局端口句柄告知Dart任务处理结束

在单元测试中,我尝试模拟对C的启停调用,但不知道如何获取Dart的SendPort来通知另一个函数中的监听器任务完成。查阅Mockito、Flutter、FFI相关文档后,尝试用IsolateNameServer.lookupPortByName获取命名ReceivePort的SendPort,编写了测试代码,但最后断言sut.isRunning为false总是失败。

测试代码
test("run", () {
  when(() => clib.start).thenReturn((invocation) => (_) {});
  when(() => clib.stop).thenReturn(() => () {
    SendPort? port = IsolateNameServer.lookupPortByName("PortA");
    port?.send(1);
  });
  expect(sut.isRunning, false); 
  sut.start(); // 内部会调用clib.start
  expect(sut.isRunning, true); 
  sut.stop(); // 内部会调用clib.stop
  expect(sut.isRunning, false); // <= 这一步断言总是失败
});
问题分析

断言失败的核心原因是异步处理未等待完成:

  • 从C端通过SendPort.send()发送消息到Dart侧的ReceivePort是异步操作,sut.stop()调用后,监听器收到消息并修改isRunning状态的逻辑还没执行完,就立刻执行了最后的断言,此时状态还没更新。
  • 另外需要确认:sut.start()中是否正确将命名的ReceivePort注册到IsolateNameServer,如果注册时机不对或者没注册,lookupPortByName会返回null,导致消息根本没发送,状态自然不会改变。
解决方案

1. 确保ReceivePort正确注册

在被测试类sut的start()方法中,创建命名ReceivePort并注册到IsolateNameServer:

void start() {
  ReceivePort receivePort = ReceivePort();
  IsolateNameServer.registerPortWithName(receivePort.sendPort, "PortA");
  receivePort.listen((message) {
    if (message == 1) {
      isRunning = false;
      receivePort.close();
      IsolateNameServer.removePortNameMapping("PortA");
    }
  });
  // 调用C的start函数,传递receivePort.sendPort.nativePort
  clib.start(receivePort.sendPort.nativePort);
}

2. 等待异步消息处理完成

单元测试中需要等待消息处理完成后再断言,推荐用Completer监听状态变化:

test("run", () async {
  when(() => clib.start).thenReturn((invocation) => (_) {});
  final completer = Completer<void>();
  
  when(() => clib.stop).thenReturn(() => () {
    SendPort? port = IsolateNameServer.lookupPortByName("PortA");
    port?.send(1);
  });

  expect(sut.isRunning, false);
  sut.start();
  expect(sut.isRunning, true);
  
  // 假设sut暴露了状态停止的流,若无则可在测试中临时监听ReceivePort
  sut.onStop.listen(() => completer.complete());
  
  sut.stop();
  await completer.future; // 等待状态更新完成
  expect(sut.isRunning, false);
});

如果sut未暴露状态流,也可使用测试专用的waitFor(注意设置超时避免阻塞):

test("run", () {
  when(() => clib.start).thenReturn((invocation) => (_) {});
  when(() => clib.stop).thenReturn(() => () {
    SendPort? port = IsolateNameServer.lookupPortByName("PortA");
    port?.send(1);
  });

  expect(sut.isRunning, false);
  sut.start();
  expect(sut.isRunning, true);
  
  sut.stop();
  
  // 等待状态更新,最多等待1秒
  waitFor(() => sut.isRunning == false, timeout: Duration(seconds: 1));
  expect(sut.isRunning, false);
});

3. 验证SendPort是否成功获取

可在模拟的clib.stop中添加日志,确认端口是否获取成功:

when(() => clib.stop).thenReturn(() => () {
  SendPort? port = IsolateNameServer.lookupPortByName("PortA");
  print("端口查找结果:${port != null}"); // 查看是否获取到端口
  port?.send(1);
});

内容的提问来源于stack exchange,提问作者fdollack

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 15:15:36