Flutter API异步错误导致getUsers接口失败测试未通过
我正在用测试驱动开发(TDD)实现AuthenticationRemoteDataSource,这是和API交互的上层实现,包含createUser和getUsers两个方法。createUser的成功、失败测试都通过了,getUsers的成功测试也没问题,但失败状态的测试始终失败,找不到问题所在。
实现代码
AuthenticationRemoteDataSourceImplementation中的getUsers方法:
Future<List<UserModel>> getUsers() async { try { final response = await _client.get(Uri.parse('$kBaseUrl$kGetUsersEndpoint')); if (response.statusCode != 200) { throw ( APIException( message: response.body, statusCode: response.statusCode, ), ); } return List<DataMap>.from(jsonDecode(response.body) as List) .map((userdata) => UserModel.fromMap(userdata)) .toList(); } on APIException { rethrow; } catch (e) { throw APIException(message: e.toString(), statusCode: 505); } }
注:DataMap即Map<String, dynamic>,_client是http.Client实例,APIException是包含消息和状态码的自定义异常。
测试代码
getUsers的测试用例:
group('getUsers', () { const testList = [UserModel.empty()]; test('Should return a [List<UserModel>] when the status code is 200', () async { // stubbing when( () => client.get( any(), ), ).thenAnswer((_) async => http.Response(jsonEncode([testList.first.toMap()]), 200)); // act final result = await remoteDataSource.getUsers(); // assert expect(result, equals(testList)); verify(() => client.get(Uri.parse('$kBaseUrl$kGetUsersEndpoint'))) .called(1); verifyNoMoreInteractions(client); }); // 失败的测试用例 test('Should throw an [APIException] when the status code is not 200', () async { // stubbing when(() => client.get(any())).thenAnswer( (_) async => http.Response( 'Server is down', 505, ), ); // act final methodCall = remoteDataSource.getUsers; // assert expect( () => methodCall(), throwsA( const APIException( message: 'Server is down', statusCode: 505, ), ), ); verify( () => client.get( Uri.parse('$kBaseUrl$kGetUsersEndpoint'), ), ).called(1); verifyNoMoreInteractions(client); }); });
报错信息
Expected: throws APIException:<APIException(Server is down, 505)> Actual: <Closure: () => Future<List<UserModel>>> Which: threw APIException:<APIException((APIException(Server is down, 505)), 505)> stack package:cleanarchtdd/src/authentication/data/datasources/authentication_remote_data_source.dart 78:7 AuthenticationRemoteDataSourceImplementation.getUsers ===== asynchronous gap =========================== package:matcher expect package:flutter_test/src/widget_tester.dart 454:18 expect test\src\authentication\data\datasources\authentication_remote_data_source_test.dart 147:7 main.<fn>.<fn> package:matcher expect package:flutter_test/src/widget_tester.dart 454:18 expect test\src\authentication\data\datasources\authentication_remote_data_source_test.dart 147:7 main.<fn>.<fn>
我试过在when和verify里加async关键字,但引发了其他错误,而且这个测试的写法和createUser的失败测试完全一致。
问题原因及修复方案
核心问题:throw语句的多余括号
从报错信息里的APIException((APIException(Server is down, 505)), 505)能看出,实际抛出的异常是把原本的APIException作为了新异常的message。原因是getUsers里的throw语句多了一对括号:// 错误写法:抛出的是一个包含APIException的元组 throw ( APIException( message: response.body, statusCode: response.statusCode, ), );这会导致抛出的不是
APIException实例,而是一个元组,这个元组会被后续的catch (e)捕获,然后被包装成一个新的APIException,message就是元组的字符串形式。修复成:
// 正确写法:直接抛出APIException实例 throw APIException( message: response.body, statusCode: response.statusCode, );异步测试的写法优化
测试中的异步异常断言,建议用expectLater来确保异步操作完成后再断言,避免时序问题:// 替换原来的expect代码 await expectLater( methodCall(), throwsA(const APIException(message: 'Server is down', statusCode: 505)), );
完成这两处修改后,失败的测试应该就能通过了。
内容的提问来源于stack exchange,提问作者byraychen

