Dart单元测试verify调用不匹配:MockRequest相等性实现咨询
你遇到的核心问题是:测试中verify用的自定义MockRequest实例和sendEmail方法里生成的原生http.Request实例,默认情况下无法通过相等性校验,导致mockito判定调用不匹配。另外代码里还有个容易忽略的细节——请求头的大小写不一致,这也会影响匹配结果。
先修复请求头大小写问题
你的sendEmail方法里设置的请求头是"Content-type": "text/plain"(小写t),但测试中MockRequest的headers写的是'Content-Type': 'text/plain'(大写T)。虽然Http协议中请求头键不区分大小写,但Map的相等性比较是区分大小写的,这会直接导致匹配失败。先把测试里的请求头键改成和业务代码一致的"Content-type"。
方案一:用Equatable实现MockRequest的相等性校验
修改你的MockRequest类,通过EquatableMixin自定义相等性判断的字段,确保两个请求对象的核心属性一致时就判定为相等:
import 'package:equatable/equatable.dart'; import 'package:http/http.dart' as http; import 'package:mockito/mockito.dart'; class MockRequest extends Mock with EquatableMixin implements http.Request { @override List<Object?> get props => [ method, url, // 将HttpHeaders转为普通Map,并统一键为小写,避免大小写干扰 Map.fromEntries(headers.entries.map((e) => MapEntry(e.key.toLowerCase(), e.value))), body, ]; }
这样修改后,只要两个请求的method、url、请求头(忽略大小写)、body都一致,MockRequest就会和原生http.Request判定为相等,verify就能匹配成功。
方案二:使用自定义匹配器(更灵活,无需MockRequest)
如果你不想引入Equatable,也可以直接用mockito的predicate自定义匹配逻辑,不用创建MockRequest实例:
- 在测试文件里定义匹配器函数:
import 'package:matcher/matcher.dart'; import 'package:http/http.dart' as http; Matcher isExpectedRequest(Uri expectedUrl, String expectedEmail) { return predicate((http.Request actualRequest) { // 统一请求头键为小写,消除大小写差异 final actualHeaders = actualRequest.headers.map((key, value) => MapEntry(key.toLowerCase(), value)); return actualRequest.method == 'GET' && actualRequest.url == expectedUrl && actualHeaders['content-type'] == 'text/plain' && actualRequest.body == '{"email":"$expectedEmail"}'; }, "匹配预期的请求参数"); }
- 修改测试中的
verify代码:
verify(() => client.send(argThat(isExpectedRequest(uri, validEmail)))).called(1);
这种方式直接验证send方法收到的实际请求对象是否符合预期,不需要额外创建MockRequest,逻辑更直观。
为什么原来的代码会失败?
mockito默认用==运算符比较传入的参数,而你在测试里创建的MockRequest实例和sendEmail里生成的原生http.Request实例是完全不同的对象,默认的==比较的是对象引用,所以即使属性一致,也会判定为不相等,导致出现"No matching calls"的错误。
内容的提问来源于stack exchange,提问作者zex_rectooor

