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

Flutter中Mockito无法验证带jsonEncode的client.post请求问题

Mockito无法验证使用jsonEncode编码body的POST请求调用

我在为一个简单的POST请求编写测试时遇到问题:当请求body用jsonEncode编码后,Mockito的verify方法无法验证client.post是否被实际调用。


未使用编码时测试正常运行

测试代码

// 其他导入
import 'auth_data_source_test.mocks.dart';

@GenerateMocks([http.Client])
void main() {
  late AuthRemoteDataSourceImpl datasource;
  late MockClient mockHttClient;

  setUp(() {
    mockHttClient = MockClient();
    datasource = AuthRemoteDataSourceImpl(client: mockHttClient);
  });

  group('login', () {
    const tUsername = 'user@example.com';
    const tPassword = 'myPassword';
    const tEndpoint = 'http://app.griot.me/api/user/auth/';
    const tBody = {
      "username": tUsername,
      "password": tPassword,
    };

    const tHeaders = {
      'Content-Type': 'application/json',
    };
    final tTokenModel =
        TokenModel.fromJson(json.decode(fixture('auth_success_response.json')));

    test(
      'Should perform a POST request with user/auth/ endpoint and application/json header',
      () async {
        // arrange
        when(mockHttClient.post(Uri.parse(tEndpoint),
                headers: tHeaders, body: tBody))
            .thenAnswer((_) async =>
                http.Response(fixture('auth_success_response.json'), 200));

        datasource.login(tUsername, tPassword);
        // assert
        verify(mockHttClient.post(Uri.parse(tEndpoint),
            headers: tHeaders,
            body: {'username': tUsername, 'password': tPassword}));
      },
    );
}

实现代码

// 其他导入

abstract class AuthRemoteDataSource {
  Future<TokenModel> login(String username, String password);
  Future<void> storeToken(TokenModel tokenToStore);
}

class AuthRemoteDataSourceImpl implements AuthRemoteDataSource {
  final http.Client client;

  AuthRemoteDataSourceImpl({required this.client});

  @override
  Future<TokenModel> login(String username, String password) async {
    final Map<String, dynamic> body = {
      'username': username,
      'password': password,
    };

    final response =
        await client.post(Uri.parse('http://app.griot.me/api/user/auth/'),
            headers: {
              'Content-Type': 'application/json',
            },
            body: body);

    await handleError(response);

    return TokenModel.fromJson(json.decode(response.body));
  }

  @override
  Future<void> storeToken(TokenModel tokenToStore) async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    await prefs.setString('token', tokenToStore.tokenString);
  }

  Future<void> handleError(http.Response response) async {
    if (response.statusCode == 401 || response.statusCode == 404) {
      throw InvalidTokenException();
    } else if (!(response.statusCode >= 200 && response.statusCode <= 204)) {
      throw ServerException();
    }
  }
}

使用jsonEncode后测试失败

修改后的实现代码(仅login方法部分)

@override
Future<TokenModel> login(String username, String password) async {
  final Map<String, dynamic> body = {
    'username': username,
    'password': password,
  };

  final response =
      await client.post(Uri.parse('http://app.griot.me/api/user/auth/'),
          headers: {
            'Content-Type': 'application/json',
          },
          body: jsonEncode(body)); // 改为jsonEncode编码

  await handleError(response);

  return TokenModel.fromJson(json.decode(response.body));
}

修改后的测试代码(仅arrange部分)

// arrange
when(mockHttClient.post(Uri.parse(tEndpoint),
        headers: tHeaders, body: jsonEncode(tBody)))
    .thenAnswer((_) async =>
        http.Response(fixture('auth_success_response.json'), 200));

错误信息

No matching calls. All calls: MockClient.post(http://app.griot.me/api/user/auth/, {headers: {Content-Type: application/json}, body: {"username":"user@example.com","password":"myPassword"}, encoding: null})

已尝试重新运行dart run build_runner build生成Mock文件,但问题仍未解决。


解决方法

问题出在Mockito的参数匹配逻辑上:当使用jsonEncode后,虽然字符串内容看起来一致,但可能存在空格、引号格式或者编码细节差异,导致严格匹配失败。可以用以下方式解决:

1. 预计算编码字符串并复用

提前生成编码后的字符串,避免多次调用jsonEncode可能产生的细微差异:

// 在group块外或setUp中预计算
final tEncodedBody = jsonEncode(tBody);

// 测试中使用预计算的字符串
when(mockHttClient.post(Uri.parse(tEndpoint),
        headers: tHeaders, body: tEncodedBody))
    .thenAnswer((_) async =>
        http.Response(fixture('auth_success_response.json'), 200));

verify(mockHttClient.post(Uri.parse(tEndpoint),
    headers: tHeaders,
    body: tEncodedBody));

2. 使用argThat进行宽松匹配

通过argThat结合equals方法,验证内容一致性而非严格字符串匹配:

// arrange
when(mockHttClient.post(Uri.parse(tEndpoint),
        headers: tHeaders, body: argThat(equals(jsonEncode(tBody)))))
    .thenAnswer((_) async =>
        http.Response(fixture('auth_success_response.json'), 200));

// assert
verify(mockHttClient.post(Uri.parse(tEndpoint),
    headers: tHeaders,
    body: argThat(equals(jsonEncode(tBody)))));

3. 使用any跳过body内容验证(仅验证调用行为)

如果不需要验证body具体内容,可直接用any匹配参数:

// arrange
when(mockHttClient.post(Uri.parse(tEndpoint),
        headers: tHeaders, body: any))
    .thenAnswer((_) async =>
        http.Response(fixture('auth_success_response.json'), 200));

// assert
verify(mockHttClient.post(Uri.parse(tEndpoint),
    headers: tHeaders,
    body: any));

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 02:52:46