【问题标题】:Unit test - How to mock parameters of third-party-library class Dio in flutter单元测试 - 如何在颤振中模拟第三方库类 Dio 的参数
【发布时间】:2020-08-02 22:02:11
【问题描述】:

我正在尝试测试一个简单的存储库类,它使用作为依赖注入的 Dio 包进行网络调用。 Http.post 的要求是将 Map 对象发送到带有 'Content-Type': 'application/json 标题的 URL。你可以在下面看到这个:

class AuthenticateUserRemoteDataSourceImpl
    implements AuthenticateUserRepository {
  final Dio httpClient;

  AuthenticateUserRemoteDataSourceImpl({@required this.httpClient});

  @override
  Future<Either<Failure, AuthenticateUser>> getAuthenticateUser(
      String email, String password) async {
    final url = 'API_URL';

    final Map<String, String> jsonPayload = {
      "email": email,
      "password": password
    };

    Response response = await httpClient.post('{$url}api/users/authenticate',
        data: jsonPayload,
        options: Options(headers: {'Content-Type': 'application/json'}));
  }
}

我正在尝试确保单元测试涵盖此方法,但是我很难使用 Dio 包验证命名参数。我可以验证 dioClient.post 确实被调用了,但是我在模拟“数据”和“选项”的名称参数时遇到了麻烦。

我想测试它是否被Map&lt;String, String&gt; 调用为正文,我还想测试发送的标头,我想检查它是用{'Content-Type': 'application/json'} 调用的。当我需要对 Authenticated dio get/post/put 请求进行单元测试时,这也很有用。

这是我到目前为止所做的测试,如前所述,我可以验证是否调用了模拟函数,但不能验证名称参数。


  group('getAuthenticateUser', () {
    final tResponse = Response(statusCode: 200, data: {"title": "success"});

    test(
        'should perform a post request to the the API with the application/json header',
        () async {
      // arrange
      when(dioClient.post(any,
              data: anyNamed('data'), options: anyNamed('options')))
          .thenAnswer((Invocation invocation) async => tResponse);

      // act
      await dataSource.getAuthenticateUser(tEmail, tPassword);

      // assert
      verify(dioClient.post(any,
          data: anyNamed('data'), options: anyNamed('options')));
    });
  });

感谢您的帮助,我刚刚开始进行单元测试,所以任何帮助或指示都会很棒,

谢谢,山姆

@LoVe

更新

我已经实现了你的模拟类,效果很好,我认为这是我肯定缺少的东西。我已经更新了我的测试,但不明白我现在哪里出错了?

test.dart

class MockOptions extends Mock implements Options {
  final Map<String, dynamic> headers;
  //also add any other parameters you want to mock as fields in this class

  MockOptions({this.headers});
}


test(
   'should perform a post request to the the API with the application/json header',
    () async {
        // arrange
        Map<String, String> headers = {'Content-type': 'application/json'};

        when(mockDio.post('path', options: anyNamed('options')))
          .thenAnswer((_) async => Response(data: {}));

        // act
        dataSource.getAuthenticateUser(tEmail, tPassword);

        // assert
        verify(mockDio.post('path', options: MockOptions(headers: headers)));
    });

方法文件如下所示:

  @override
  Future<Either<Failure, AuthenticateUser>> getAuthenticateUser(
      String email, String password) async {

    await this.httpClient.post('path',
        data: {},
        options: Options(headers: {'Content-type': 'application/json'}));
  }

【问题讨论】:

    标签: http flutter dart mockito flutter-test


    【解决方案1】:

    使用http_mock_adapter,用于模拟Dio 请求的新包。

    您可以简单地将注入的DiohttpClientAdapter 替换为DioAdapter() of http_mock_adapter

    来自exampleshttp_mock_adapter 的示例

    import 'package:dio/dio.dart';
    import 'package:http_mock_adapter/http_mock_adapter.dart';
    
    void main() async {
      final dio = Dio();
      final dioAdapter = DioAdapter();
    
      dio.httpClientAdapter = dioAdapter;
    
      const path = 'https://example.com';
    
      dioAdapter
          ..onGet(
            path,
            (request) => request.reply(200, {'message': 'Successfully mocked GET!'}),
          )
          ..onGet(
            path,
            (request) => request.reply(200, {'message': 'Successfully mocked POST!'}),
          );
    
      final onGetResponse = await dio.get(path);
      print(onGetResponse.data); // {message: Successfully mocked GET!}
    
      final onPostResponse = await dio.post(path);
      print(onPostResponse.data); // {message: Successfully mocked POST!}
    }
    

    您也可以使用DioInterceptor of http_mock_adapter,可以添加到dio.interceptor列表中。

    http_mock_adapterexamples 文件中查看第二个示例

    【讨论】:

      【解决方案2】:

      测试这个方法

      Response response = await httpClient.post('{$url}api/users/authenticate',
              data: jsonPayload,
              options: Options(headers: {'Content-Type': 'application/json'}));
      

      使用正确的参数调用你应该创建一个MockOption 类并将它的一个实例传递给你的测试中的函数调用

      您还应该创建Map&lt;String,dynamic&gt; 类型的对象(或json 使用的任何类型)并将该对象也传递给data 参数的相同方法调用

      然后你使用Mockito 的方法来验证你的方法是用你传递的测试参数调用的

      并且还测试你在此处所说的关于地图的标题:

      您还应该创建一个Map&lt;String,dynamic&gt; 类型的对象(或 json 使用的任何类型)并将该对象也传递给相同的 方法调用

      请看我的回答here

      更新:

      例如模拟options 参数,您可以这样做:

      class MockOptions extends Mock implements Options{
        final Map<String,dynamic> headers;
        //also add any other parameters you want to mock as fields in this class
      
        MockOptions(this.headers);
      }
      

      然后,在你的测试中,你这样做:

      final MockOptions mockOptions = MockOptions({
                    //mocked map data here
                  });
      

      【讨论】:

      • 嗨,小伙子,谢谢你的回答。我仍然无法弄清楚这一点。休息了几个星期后我又回来了,但仍然没有快乐。我现在已经放了一大笔钱,如果可能的话,你能否给我更多的说明模拟响应的样子?当我尝试模拟 Options 类时,我无法向该类添加任何命名参数,这是对 Mock 类的限制吗?
      • 你的意思是你不能用例如data: myData替换data: anyNamed('data')
      • 我在原问题上添加了截图
      • 谢谢,我没想到要在模拟类中添加参数。很好
      • 我已经更新了我的问题,在使用您的命名参数解决方案后,您能发现我更新的测试有什么问题吗?在when 函数中,我传递了anyNamed,因为这不一定是MockOptions 的实例。我只需要验证是否使用正确的选项调用了该方法。还是我又误会了?
      猜你喜欢
      • 1970-01-01
      • 2021-07-22
      • 1970-01-01
      • 2020-12-04
      • 2021-05-21
      • 2021-09-25
      • 1970-01-01
      • 1970-01-01
      • 2021-01-07
      相关资源
      最近更新 更多