如何在无直接访问权限时获取命名ReceivePort的SendPort用于测试?
问题描述
我正在进行单元测试,尝试模拟一个通过FFI调用C函数的类。C中有一对负责启动和停止线程的函数:
- 启动函数会接收
nativePort,用于和线程通信,C端会全局存储该端口句柄并启动线程处理任务 - 停止函数会发出信号让线程结束,随后线程会使用存储的
nativePort句柄通知Dart侧的监听器任务已完成
具体流程:
- Dart侧的
DartFunc1()通过ReceivePort.SendPort.nativePort将端口传递给C原生库 - C库全局存储端口句柄并启动线程处理任务
- 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
相关产品推荐
相关产品推荐

