Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mocking dio : Could not find mocked route matching #96

Open
tazik561 opened this issue Apr 24, 2021 · 16 comments
Open

Mocking dio : Could not find mocked route matching #96

tazik561 opened this issue Apr 24, 2021 · 16 comments
Assignees
Labels
bug Something isn't working medium priority Issue has medium priority

Comments

@tazik561
Copy link

I am trying to make unit test with mockitto to test dio.

void main() {
  group("Splash init", () {
    DioAdapterMockito dioAdapterMockito;
    Dio dio;
    const path = 'https://..../mobile/';

    setUpAll(() {
      dioAdapterMockito = DioAdapterMockito();
      dio = Dio()..httpClientAdapter = dioAdapterMockito;
    });

test(
    "call getMainConfigs -> getMainConfig web service called 404 exception",
    () async {
  final dioError = DioError(
    error: {'message': 'Some beautiful error!'},
    request: RequestOptions(path: path),
    response: Response(
      statusCode: 500,
      request: RequestOptions(path: path),
    ),
    type: DioErrorType.RESPONSE,
  );

  dioInterceptor.onPost(path, (request) => request.throws(500, dioError),
      headers: {'Content-Type': 'application/json; charset=utf-8'});
  expect(() async => await dio.post(path), throwsA(isA<AdapterError>()));
  // expect(() async => await dio.get(path), throwsA(isA<DioError>()));
  // expect(
  //   () async => await dio.get(path),
  //   throwsA(
  //     predicate(
  //       (DioError error) =>
  //           error is DioError &&
  //           error is AdapterError &&
  //           error.message == dioError.error.toString(),
  //     ),
  //   ),
  // );
});
  });
}

but after running this test I got this error:

Expected: throws <Instance of 'AdapterError'>
  Actual: <Closure: () => Future<Response<dynamic>>>
   Which: threw DioError:<DioError [DioErrorType.DEFAULT]: Assertion failed: "Could not find mocked route matching request for https://..../mobile//POST/null/{}/{content-type: application/json; charset=utf-8}"
#0      History.responseBody.<anonymous closure>
package:http_mock_adapter/src/history.dart:32
#1      DioInterceptor.onRequest
package:http_mock_adapter/…/interceptors/dio_interceptor.dart:63
#2      DioMixin._request._interceptorWrapper.<anonymous closure>.<anonymous closure>.<anonymous closure>
package:dio/src/dio.dart:849
#3      DioMixin.checkIfNeedEnqueue
package:dio/src/dio.dart:1121
#4      DioMixin._request._interceptorWrapper.<anonymous closure>.<anonymous closure>
package:dio/src/dio.dart:846
#5      new Future.<anonymous closure> (dart:async/future.dart:174:37)
#6      StackZoneSpecification._run
package:stack_trace/src/stack_zone_specification.dart:208
#7      StackZoneSpecification._registerCallback.<anonymous closure>
package:stack_trace/src/stack_zone_specification.dart:116

I am trying to test exception.

@tazik561 tazik561 added the bug Something isn't working label Apr 24, 2021
@theiskaa
Copy link
Contributor

theiskaa commented Apr 24, 2021

Hi @tazik561 !

The problem is you're throwing a AdapterError as expected value:

expect(() async => await dio.post(path), throwsA(isA<AdapterError>()));

But you was given a DioError into dioAdapterMockito.onPost method:

  dioAdapterMockito.onPost(
        path,
        (request) => request.throws(500, dioError),
        headers: {'Content-Type': 'application/json; charset=utf-8'},
      );

So I mean you should change your expected value as DioError, like:

  expect(() async => await dio.post(path), throwsA(isA< DioError>()));

The full test code:
Note: I am using the latest version of the packages

void main() {
  group("Splash init", () {
    DioAdapterMockito dioAdapterMockito;
    Dio dio;
    const path = 'https://..../mobile/';

    setUpAll(() {
      dioAdapterMockito = DioAdapterMockito();
      dio = Dio()..httpClientAdapter = dioAdapterMockito;
    });

    test(
        "call getMainConfigs -> getMainConfig web service called 404 exception",
        () async {
      final dioError = DioError(
        error: {'message': 'Some beautiful error!'},
        requestOptions: RequestOptions(path: path),
        response: Response(
          statusCode: 500,
          requestOptions: RequestOptions(path: path),
        ),
        type: DioErrorType.response,
      );

      dioAdapterMockito.onPost(
        path,
        (request) => request.throws(500, dioError),
        headers: {'Content-Type': 'application/json; charset=utf-8'},
      );
      expect(() async => await dio.post(path), throwsA(isA<AdapterError>()));
      // expect(() async => await dio.get(path), throwsA(isA<DioError>()));
      // expect(
      //   () async => await dio.get(path),
      //   throwsA(
      //     predicate(
      //       (DioError error) =>
      //           error is DioError &&
      //           error is AdapterError &&
      //           error.message == dioError.error.toString(),
      //     ),
      //   ),
      // );
    });
  });
}

Correct and clean usage of http-mock-adapter should be:

void main() {
  DioAdapter dioAdapterMockito;
  Dio dio;

  DioError dioError;

  const path = 'https://example.com';

  setUpAll(() {
    dioAdapterMockito = DioAdapterMockito();
    dio = Dio();

    dio.httpClientAdapter = dioAdapterMockito;

    DioError(
      error: {'message': 'Some beautiful error!'},
      requestOptions: RequestOptions(path: path),
      response: Response(
        statusCode: 500,
        requestOptions: RequestOptions(path: path),
      ),
      type: DioErrorType.response,
    );
  });
  group("Splash init", () {
    test(
        "call getMainConfigs -> getMainConfig web service called 404 exception",
        () async {
      dioAdapterMockito.onPost(
        path,
        (request) => request.throws(500, dioError),
        headers: {'Content-Type': 'application/json; charset=utf-8'},
      );

      expect(() async => await dio.post(path), throwsA(isA<DioError>()));
    });
  });
}

@tazik561
Copy link
Author

Hi @tazik561 !

The problem is you're throwing a AdapterError as expected value:

expect(() async => await dio.post(path), throwsA(isA<AdapterError>()));

But you was given a DioError into dioAdapterMockito.onPost method:

  dioAdapterMockito.onPost(
        path,
        (request) => request.throws(500, dioError),
        headers: {'Content-Type': 'application/json; charset=utf-8'},
      );

So I mean you should change your expected value as DioError, like:

  expect(() async => await dio.post(path), throwsA(isA<AdapterError>()));

The full test code:
Note: I am using the latest version of the packages

void main() {
  group("Splash init", () {
    DioAdapterMockito dioAdapterMockito;
    Dio dio;
    const path = 'https://..../mobile/';

    setUpAll(() {
      dioAdapterMockito = DioAdapterMockito();
      dio = Dio()..httpClientAdapter = dioAdapterMockito;
    });

    test(
        "call getMainConfigs -> getMainConfig web service called 404 exception",
        () async {
      final dioError = DioError(
        error: {'message': 'Some beautiful error!'},
        requestOptions: RequestOptions(path: path),
        response: Response(
          statusCode: 500,
          requestOptions: RequestOptions(path: path),
        ),
        type: DioErrorType.response,
      );

      dioAdapterMockito.onPost(
        path,
        (request) => request.throws(500, dioError),
        headers: {'Content-Type': 'application/json; charset=utf-8'},
      );
      expect(() async => await dio.post(path), throwsA(isA<AdapterError>()));
      // expect(() async => await dio.get(path), throwsA(isA<DioError>()));
      // expect(
      //   () async => await dio.get(path),
      //   throwsA(
      //     predicate(
      //       (DioError error) =>
      //           error is DioError &&
      //           error is AdapterError &&
      //           error.message == dioError.error.toString(),
      //     ),
      //   ),
      // );
    });
  });
}

Correct and clean use of http-mock-adapter should be:

void main() {
  DioAdapter dioAdapterMockito;
  Dio dio;

  DioError dioError;

  const path = 'https://example.com';

  setUpAll(() {
    dioAdapterMockito = DioAdapterMockito();
    dio = Dio();

    dio.httpClientAdapter = dioAdapterMockito;

    DioError(
      error: {'message': 'Some beautiful error!'},
      requestOptions: RequestOptions(path: path),
      response: Response(
        statusCode: 500,
        requestOptions: RequestOptions(path: path),
      ),
      type: DioErrorType.response,
    );
  });
  group("Splash init", () {
    test(
        "call getMainConfigs -> getMainConfig web service called 404 exception",
        () async {
      dioAdapterMockito.onPost(
        path,
        (request) => request.throws(500, dioError),
        headers: {'Content-Type': 'application/json; charset=utf-8'},
      );

      expect(() async => await dio.post(path), throwsA(isA<DioError>()));
    });
  });
}

Thank . I changed my code to this:

  group("Dio Exception", () {
    DioAdapterMockito dioAdapterMockito;
    Dio dio;
    DioError dioError;
     const path = 'https://..../mobile/';

    setUpAll(() {
      dioAdapterMockito = DioAdapterMockito();
      dio = Dio()..httpClientAdapter = dioAdapterMockito;

      dioError = DioError(
        error: {'message': 'Some beautiful error!'},
        request: RequestOptions(
          path: path,
        ),
        response: Response(
          statusCode: 500,
          request: RequestOptions(path: path),
        ),
        type: DioErrorType.RESPONSE,
      );
    });
    test(
        "call getMainConfigs -> getMainConfig web service called 404 exception",
        () async {
      dioAdapterMockito.onPost(
        path,
        (request) => request.throws(500, dioError),
        headers: {'Content-Type': 'application/json; charset=utf-8'},
      );

      var result = await dio.post(path, data: null);
      expect(() async => result, throwsA(isA<DioError>()));
    });
  });
}

But I got this error:

DioError [DioErrorType.DEFAULT]: NoSuchMethodError: The getter 'headers' was called on null.
Receiver: null
Tried calling: headers
#0      Object.noSuchMethod (dart:core-patch/object_patch.dart:54:5)
#1      DioMixin._dispatchRequest
package:dio/src/dio.dart:927
<asynchronous suspension>
#2      StackZoneSpecification._registerUnaryCallback.<anonymous closure> (package:stack_trace/src/stack_zone_specification.dart)
package:stack_trace/src/stack_zone_specification.dart:1
<asynchronous suspension>
2

DioMixin._dispatchRequest
package:dio/src/dio.dart:966

@LukaGiorgadze
Copy link
Member

@tazik561 are you using the latest version of http_mock_adapter?

@theiskaa
Copy link
Contributor

theiskaa commented Apr 24, 2021

@tazik561

Don't create a variable for await dio.post() just write it directly.
However expect method should be:

expect(() async => await dio.post(path, data: null),
          throwsA(isA<DioError>()));

Not this:

var result = await dio.post(path, data: null);
expect(() async => result, throwsA(isA<DioError>()));

And make sure you're using latest versions of dio and http_mock_adapter.

Here is the full code of your test:

group("Dio Exception", () {
    DioAdapterMockito dioAdapterMockito;
    Dio dio;
    DioError dioError;
    const path = 'https://..../mobile/';

    setUpAll(() {
      dioAdapterMockito = DioAdapterMockito();
      dio = Dio()..httpClientAdapter = dioAdapterMockito;

      dioError = DioError(
        error: {'message': 'Some beautiful error!'},
        requestOptions: RequestOptions(path: path),
        response: Response(
          statusCode: 500,
          requestOptions: RequestOptions(path: path),
        ),
        type: DioErrorType.response,
      );
    });
    test(
        "call getMainConfigs -> getMainConfig web service called 404 exception",
        () async {
      dioAdapterMockito.onPost(
        path,
        (request) => request.throws(500, dioError),
        headers: {'Content-Type': 'application/json; charset=utf-8'},
      );

      expect(() async => await dio.post(path, data: null),
          throwsA(isA<DioError>()));
    });
  });

@tazik561
Copy link
Author

tazik561 commented Apr 24, 2021

Of course not. I am using dio: ^3.0.9 and http_mock_adapter: ^0.1.6 because I don't use sounds null safety environment: sdk: ">=2.7.0 <3.0.0".

It is possible to mix DioAdapterMockito in this way ):

setupMockHttpClientFailure404(){
      dioAdapterMockito.onPost(
        path,
        (request) => request.throws(404, dioError),
        headers: {'Content-Type': 'application/json; charset=utf-8'},
      );
}
    test(
      'should throw a ServerException when the responce code is 404 or other ',
      () async {
        setupMockHttpClientFailure404();
        final call = dataSource.getConcreteNumberTrivia;
        expect(call(url), throwsA(TypeMatcher<ServerExceptions>()));
      },
    );

main method on repo class:

  Future<NumberTriviaModel> getConcreteNumberTrivia(String url) async {
    final response =
        await dio.get(url, headers: {'Content-Type': 'application/json'});

    if (response.statusCode == 200) {
      return NumberTriviaModel.fromJson(json.decode(response.body));
    } else {
      throw ServerExceptions();
    }
  }

to check a method that has dio method inside it?

@theiskaa
Copy link
Contributor

Hi @tazik561
I tried fix your problem by initializing dio: ^3.0.9 and http_mock_adapter: ^0.1.6.
And I fixed it, It's simple you just need to change your expected value to:

expect(() async => await dio.post(path), throwsA(isA<DioError>()));

Just that, and I said that in this answer.

@LukaGiorgadze
Copy link
Member

@theiskaa i guess we resolved this in #100 PR, right?

@theiskaa
Copy link
Contributor

@LukaGiorgadze no Luka, we were resolved this issue with #100 PR. @tazik561 's issue needn't any changes on http-mock-adapter, he just should do this , so the problem is his test code.

@tazik561
Copy link
Author

tazik561 commented Apr 30, 2021

Hi @tazik561
I tried fix your problem by initializing dio: ^3.0.9 and http_mock_adapter: ^0.1.6.
And I fixed it, It's simple you just need to change your expected value to:

expect(() async => await dio.post(path), throwsA(isA<DioError>()));

Just that, and I said that in this answer.

I don't want to test dio directly . As I mention above I have a method called : getConcreteNumberTrivia.

  Future<NumberTriviaModel> getConcreteNumberTrivia(String url) async {
    final response =
        await dio.get(url, headers: {'Content-Type': 'application/json'});

    if (response.statusCode == 200) {
      return NumberTriviaModel.fromJson(json.decode(response.body));
    } else {
      throw ServerExceptions();
    }
  }

I am trying write test for this method getConcreteNumberTrivia. Inside this method there is a dio .Now I want when parser rich to dio, dio throw an error or exception .

In this answer we just test dio directly. But I want to test a method that has dio part like repository layer.

for example

setupMockHttpClientFailure404(){
      dioAdapterMockito.onPost(
        path,
        (request) => request.throws(404, dioError),
        headers: {'Content-Type': 'application/json; charset=utf-8'},
      );
}
    test(
      'should throw a ServerException when the responce code is 404 or other ',
      () async {
        setupMockHttpClientFailure404();
        final call = dataSource.getConcreteNumberTrivia;
        expect(call(url), throwsA(TypeMatcher<ServerExceptions>()));
      },
    );

I call final call = dataSource.getConcreteNumberTrivia; in test, In getConcreteNumberTrivia method when parser rich to final response = await dio.get(url, headers: {'Content-Type': 'application/json'}); , throw an exception.

@Alvarocda
Copy link

same problem here

@erayerdin
Copy link

Well, I'm trying to use this with get_it. I register my client with:

getIt.registerLazySingleton(
      () => BaseOptions(baseUrl: 'https://www.bscotch.net/api/levelhead'),
      instanceName: 'rumpusClientBaseOptions');
  getIt.registerLazySingleton(
      () => Dio(getIt.get(instanceName: 'rumpusClientBaseOptions')),
      instanceName: 'rumpusClient');

Then in setUpAll, I do:

setUpAll(() async {
      await setUpDI();

      var adapter = DioAdapter();
      adapter
        ..onGet(
            '/players',
            (request) => request.reply(200, {
                  'data': [
                    {
                      "_id": "609e5516f0b9d200b711b8b5",
                      "userId": "pvdw78",
                      "stats": {
                        "Subscribers": 0,
                        "NumFollowing": 0,
                        "Crowns": 0,
                        "Shoes": 0,
                        "PlayTime": 0,
                        "TipsPerLevel": 0,
                        "TipsPerDay": 0,
                        "TippedPerLevelPlayed": 0,
                        "TippedPerDay": 0,
                        "HiddenGem": 0,
                        "Trophies": 0,
                        "PerkPoints": 5,
                        "CampaignProg": 0,
                        "TimeTrophies": 0
                      },
                      "createdAt": "2021-05-14T10:46:46.723Z",
                      "updatedAt": "2021-05-14T10:46:48.639Z",
                      "alias": {
                        "userId": "pvdw78",
                        "alias": "LeapyimbleZiprompa",
                        "avatarId": "gr18-serious",
                        "context": "levelhead"
                      }
                    }
                  ]
                }));
      Dio client = getIt.get(instanceName: 'rumpusClient');
      client.httpClientAdapter = adapter;
    });

Since it is a lazy singleton, I'm sure I get the only one instance of Dio. I change the adapter to this mock adapter as you can see above. However, running my tests, I get the error saying:

DioError [DioErrorType.other]: Assertion failed: "Could not find mocked route matching request for /players/GET/null/{includeRecords: true, includeStats: true, userIds: foobar}/{}"

Opting out of baseUrl also does no good.


Environment

 > flutter --version
Flutter 2.0.5 • channel stable • https://github.com/flutter/flutter.git
Framework • revision adc687823a (4 weeks ago) • 2021-04-16 09:40:20 -0700
Engine • revision b09f014e96
Tools • Dart 2.12.3
  • dio 4.0.0
  • http_mock_adapter 0.2.1

@theiskaa theiskaa self-assigned this Jun 5, 2021
@theiskaa theiskaa added the medium priority Issue has medium priority label Jun 5, 2021
@theiskaa
Copy link
Contributor

theiskaa commented Jun 5, 2021

Hi @erayerdin first of all thanks for your response!

In your test code I can see you did mocking some request by this path:

/players/GET/{your data here}/{includeRecords: true, includeStats: true, userIds: foobar}/{}

and you get error by this path:

/players/GET/null/{includeRecords: true, includeStats: true, userIds: foobar}/{}

So your test code's path and real code's path are different.
However in your real code you didn't give data and that's why your data's place is null.
Just try giving same data, path and variables, if something goes wrong please tell us, write down in this issue or fill a new one.

@theiskaa
Copy link
Contributor

theiskaa commented Jun 5, 2021

Hi @tazik561
I tried fix your problem by initializing dio: ^3.0.9 and http_mock_adapter: ^0.1.6.
And I fixed it, It's simple you just need to change your expected value to:

expect(() async => await dio.post(path), throwsA(isA<DioError>()));

Just that, and I said that in this answer.

I don't want to test dio directly . As I mention above I have a method called : getConcreteNumberTrivia.

  Future<NumberTriviaModel> getConcreteNumberTrivia(String url) async {
    final response =
        await dio.get(url, headers: {'Content-Type': 'application/json'});

    if (response.statusCode == 200) {
      return NumberTriviaModel.fromJson(json.decode(response.body));
    } else {
      throw ServerExceptions();
    }
  }

I am trying write test for this method getConcreteNumberTrivia. Inside this method there is a dio .Now I want when parser rich to dio, dio throw an error or exception .

In this answer we just test dio directly. But I want to test a method that has dio part like repository layer.

for example

setupMockHttpClientFailure404(){
      dioAdapterMockito.onPost(
        path,
        (request) => request.throws(404, dioError),
        headers: {'Content-Type': 'application/json; charset=utf-8'},
      );
}
    test(
      'should throw a ServerException when the responce code is 404 or other ',
      () async {
        setupMockHttpClientFailure404();
        final call = dataSource.getConcreteNumberTrivia;
        expect(call(url), throwsA(TypeMatcher<ServerExceptions>()));
      },
    );

I call final call = dataSource.getConcreteNumberTrivia; in test, In getConcreteNumberTrivia method when parser rich to final response = await dio.get(url, headers: {'Content-Type': 'application/json'}); , throw an exception.

@tazik561 Okay, as I understand it, you have a dio and that one isn't into your method right? So you wanna test your method, then you shouldn't test dio directly, yeah that is correct. But it's interesting where you do set dio class? you have to do same thing what are you doing when you are testing dio directly. Just set you dio's httpClientAdapter and go on!

@kuhnroyal
Copy link
Collaborator

I have had several cases where the mocked route could not be found. It was always my mistake but it is hard to trace down with the current assertion errors. I suggest we introduce a custom error messages with diffs for data/headers/params, especially if there is only one mocked response. With more than one mocked response it might get a bit harder to generate a correct diff.

@maurodibert
Copy link

maurodibert commented Feb 8, 2022

Hi guys! I'm trying to solve this and there is no way!
I have this class:

import 'dart:io';
import 'package:dio/dio.dart';

/// Dio requests types
enum Method {
  /// post
  post,

  /// get
  get,

  /// put
  put,

  /// delete
  delete,

  /// patch
  patch,
}

/// {@template http_service}
/// A service for managing requests
/// {@endtemplate}
class HttpService {
  /// {@macro http_service}
  HttpService({
    required Dio httpClient,
  }) : _httpClient = httpClient {
    init(httpClient: _httpClient);
  }

  late Dio _httpClient;

  /// service initialization and configuration
  Future<HttpService> init({
    required Dio httpClient,
  }) async {
    _httpClient = httpClient;
    return this;
  }

  /// help in handling request methods
  Future<Response> request({
    required String endpoint,
    required Method method,
    Map<String, dynamic>? params,
  }) async {
    Response response;

    try {
      if (method == Method.post) {
        response = await _httpClient.post<dynamic>(endpoint, data: params);
      } else if (method == Method.delete) {
        response = await _httpClient.delete<dynamic>(endpoint);
      } else if (method == Method.patch) {
        response = await _httpClient.patch<dynamic>(endpoint);
      } else {
        response =
            await _httpClient.get<dynamic>(endpoint, queryParameters: params);
      }

      if (response.statusCode == 200) {
        return response;
      } else if (response.statusCode == 401) {
        throw Exception('Unauthorized');
      } else if (response.statusCode == 500) {
        throw Exception('Server Error');
      } else {
        throw Exception("Something does wen't wrong");
      }
    } on SocketException catch (e) {
      throw Exception('Not Internet Connection');
    } on FormatException catch (e) {
      throw Exception('Bad response format');
    } on DioError catch (e) {
      throw Exception(e);
    } catch (e) {
      throw Exception("Something wen't wrong");
    }
  }
}

And trying to test errors and exceptions with this:

test('should return a Server Error Exception', () async { 
        dioAdapter.onPost(
          '/endpoint',
          (server) => server.throws(500, Constants.dioError),
          data: <String, dynamic>{},
          headers: Constants.edamamPostHeader,
        );

        expect(
          () async => await httpService.request(
            endpoint: '/endpoint',
            method: Method.post,
            params: <String, dynamic>{},
          ),
          isA<DioError>,
        );
      });

But could not resolve correctly the expectation:

Expected: <Closure: () => TypeMatcher<DioError> from Function 'isA': static.>
  Actual: <Closure: () => Future<Response<dynamic>>>

Any thoughts? Thanks in advance!

@maurodibert
Copy link

I did this which I think is correct. What do you think?
image
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working medium priority Issue has medium priority
Projects
None yet
Development

No branches or pull requests

7 participants