Flutter - type 'Null' is not a subtype of type 'Future<Response<dynamic>>' when mocking Dio's get method

284

Finally I've got a working solution.

It seems that Dio needs a seperate mock library to mock itself using adapters. The library to use is http_mock_adapter: ^0.1.4 and its below is its link.

LINK: Http Mock Library

import 'dart:convert';

import 'package:dio/dio.dart';
import 'package:http_mock_adapter/http_mock_adapter.dart';
import 'package:test/test.dart';

void main() async {
  // How to mock with DioAdapter
  group('DioAdapter usage', () {
    // Creating dio instance for mocking.
    // For instance: you can use your own instance from injection and replace
    // dio.httpClientAdapter with mocker DioAdapter

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

    test('Expects Dioadapter to mock the data', () async {
      final dio = Dio();
      final dioAdapter = DioAdapter();

      dio.httpClientAdapter = dioAdapter;
      dioAdapter
          .onGet(path)
          .reply(200,
              {'message': 'Successfully mocked GET!'}) // only use double quotes
          .onPost(path)
          .reply(200, {'message': 'Successfully mocked POST!'});

      // Making dio.get request on the path an expecting mocked response
      final getResponse = await dio.get(path);
      expect(jsonEncode({'message': 'Successfully mocked GET!'}),
          getResponse.data);

      // Making dio.post request on the path an expecting mocked response
      final postResponse = await dio.post(path);
      expect(jsonEncode({'message': 'Successfully mocked POST!'}),
          postResponse.data);
    });

    // Alternatively you can use onRoute chain to pass custom requests
    test('Expects Dioadapter to mock the data with onRoute', () async {
      final dio = Dio();
      final dioAdapter = DioAdapter();

      dio.httpClientAdapter = dioAdapter;
      dioAdapter
          .onRoute(path, request: Request(method: RequestMethods.PATCH))
          .reply(200, {
            'message': 'Successfully mocked PATCH!'
          }) // only use double quotes
          .onRoute(path, request: Request(method: RequestMethods.DELETE))
          .reply(200, {'message': 'Successfully mocked DELETE!'});

      // Making dio.get request on the path an expecting mocked response
      final patchResponse = await dio.patch(path);
      expect(jsonEncode({'message': 'Successfully mocked PATCH!'}),
          patchResponse.data);

      // Making dio.post request on the path an expecting mocked response
      final deleteResposne = await dio.delete(path);
      expect(jsonEncode({'message': 'Successfully mocked DELETE!'}),
          deleteResposne.data);
    });
  });

  // Also, for mocking requests, you can use dio Interceptor
  group('DioInterceptor usage', () {
    // Creating dio instance for mocking.
    // For instance: you can use your own instance from injection and add
    // DioInterceptor in dio.interceptors list
    final dioForInterceptor = Dio();
    final dioInterceptor =
        DioInterceptor(); // creating DioInterceptor instance for mocking requests

    dioForInterceptor.interceptors.add(dioInterceptor);

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

    test('Expects Dioadapter to mock the data', () async {
      // Defining request types and their responses respectively with their paths
      dioInterceptor
          .onDelete(path)
          .reply(200,
              {'message': 'Successfully mocked GET!'}) // only use double quotes
          .onPatch(path)
          .reply(200, {'message': 'Successfully mocked POST!'});

      // Making dio.delete request on the path an expecting mocked response
      final getResponse = await dioForInterceptor.delete(path);
      expect(jsonEncode({'message': 'Successfully mocked GET!'}),
          getResponse.data);

      // Making dio.patch request on the path an expecting mocked response
      final postResposne = await dioForInterceptor.patch(path);
      expect(jsonEncode({'message': 'Successfully mocked POST!'}),
          postResposne.data);
    });
  });

  group('Raising the custrom Error onRequest', () {
    const path = 'https://example.com';

    test('Test that throws raises custom exception', () async {
      final dio = Dio();
      final dioAdapter = DioAdapter();

      dio.httpClientAdapter = dioAdapter;
      const type = DioErrorType.RESPONSE;
      final response = Response(statusCode: 500);
      const error = 'Some beautiful error';

      // Building request to throw the DioError exception
      // on onGet for the specific path
      dioAdapter.onGet(path).throws(
            500,
            DioError(
              type: type,
              response: response,
              error: error,
            ),
          );

      // Checking that exception type can match `AdapterError` type too
      expect(() async => await dio.get(path),
          throwsA(TypeMatcher<AdapterError>()));

      // Checking that exception type can match `DioError` type too
      expect(() async => await dio.get(path), throwsA(TypeMatcher<DioError>()));

      // Checking the type and the message of the exception
      expect(
          () async => await dio.get(path),
          throwsA(
              predicate((DioError e) => e is DioError && e.message == error)));
    });
  });
}
Share:
284
Ariel
Author by

Ariel

Updated on January 02, 2023

Comments

  • Ariel
    Ariel over 1 year

    I am trying to mock Dio's get method. The mock is working fine as per my test. However, when calling inside the test type 'Null' is not a subtype of type 'Future<Response<dynamic>>'.

    I have called newsApi.get('/top-headlines') during test as well. And, I can assure it that the mock is returning data fine. But for unknown reasons, the call inside NewsService is giving null. Could you please guide me in solving the issue?

    Filename: services/news.dart

    import 'dart:io';
    
    import 'package:dio/dio.dart';
    import 'package:newsapp/enums/news_category.dart';
    import 'package:newsapp/enums/news_country.dart';
    import 'package:newsapp/models/articles.dart';
    import 'package:newsapp/models/error.dart';
    
    import '../main.dart';
    
    class NewsService {
      final Dio newsApi;
    
      NewsService({required this.newsApi});
    
      Future<dynamic> getArticlesByCategory(
        NewsCategory category, {
        int page = 1,
        int pageSize = 100,
        NewsCountry country = NewsCountry.US,
      }) async {
        final response = await newsApi.get('top-headlines', queryParameters: {
          'category': category.name,
          'country': country.name.toLowerCase(),
          'page': page,
          'pageSize': pageSize,
        });
    
        print(response);
    
        await newsApi.get('top-headlines', queryParameters: {
          'category': category.name,
          'country': country.name.toLowerCase(),
          'page': page,
          'pageSize': pageSize,
        }).then((response) {
          if (response.statusCode == HttpStatus.ok) {
            if (response.data['status'] == 'ok') {
              return Articles.fromJson(response.data);
            } else {
              return Error.fromJson(response.data);
            }
          } else if (response.statusCode == HttpStatus.unauthorized) {
            return Error.fromJson(response.data);
          } else {
            return Future.error(
                'Failure processing request. Please try again later.');
          }
        }, onError: (error) {
          print(error);
          logger.e(error);
          return Future.error(error);
        }).catchError((error) {
          print(error);
          logger.e(error);
          return error;
        });
      }
    }
    
    

    Filename: test/news.dart

    import 'dart:io';
    
    import 'package:dio/dio.dart';
    import 'package:flutter_test/flutter_test.dart';
    import 'package:mocktail/mocktail.dart';
    import 'package:newsapp/enums/news_category.dart';
    import 'package:newsapp/models/articles.dart';
    import 'package:newsapp/services/news.dart';
    
    import '../mocks/dio.dart';
    
    void main() async {
      group('NewsService tests', () {
        //Arrange
        late MockDio newsApi;
    
        group('NewsService.getArticles() tests', () {
          setUp(() {
            newsApi = MockDio();
    
            Future<Response> responseMethod = Future.value(Response(
                data: {
                  "status": "ok",
                  "totalResults": 11207,
                  "articles": [
                    {
                      "source": {"id": "bbc-news", "name": "BBC News"},
                      "author": "https://www.facebook.com/bbcnews",
                      "title": "Indian PM Modi's Twitter hacked with bitcoin tweet",
                      "description":
                          "The Indian prime minister's account had a message stating that bitcoin would be distributed to citizens.",
                      "url": "https://www.bbc.co.uk/news/world-asia-india-59627124",
                      "urlToImage":
                          "https://ichef.bbci.co.uk/news/1024/branded_news/5998/production/_122063922_mediaitem122063921.jpg",
                      "publishedAt": "2021-12-12T10:59:57Z",
                      "content":
                          "Image source, AFP via Getty Images\r\nImage caption, Modi has has more than 70 million Twitter followers\r\nIndian Prime Minister Narendra Modi's Twitter account was hacked with a message saying India ha… [+854 chars]"
                    },
                    {
                      "source": {"id": null, "name": "New York Times"},
                      "author": "Corey Kilgannon",
                      "title": "Why New York State Is Experiencing a Bitcoin Boom",
                      "description":
                          "Cryptocurrency miners are flocking to New York’s faded industrial towns, prompting concern over the environmental impact of huge computer farms.",
                      "url":
                          "https://www.nytimes.com/2021/12/05/nyregion/bitcoin-mining-upstate-new-york.html",
                      "urlToImage":
                          "https://static01.nyt.com/images/2021/11/25/nyregion/00nybitcoin5/00nybitcoin5-facebookJumbo.jpg",
                      "publishedAt": "2021-12-06T00:42:28Z",
                      "content":
                          "The plant opening northeast of Niagara Falls this month, in Somerset, N.Y., is part of a \$550 million project by Terawulf, a Bitcoin mining company. The project also includes a proposed 150-megawatt … [+1514 chars]"
                    }
                  ]
                },
                statusCode: HttpStatus.ok,
                requestOptions: RequestOptions(path: '/top-headlines')));
    
            when(() => newsApi.get(
                  '/top-headlines',
                  queryParameters: any(named: 'queryParameters'),
                  options: any(named: 'options'),
                  cancelToken: any(named: 'cancelToken'),
                  onReceiveProgress: any(named: 'onReceiveProgress'),
                )).thenAnswer((_) => responseMethod);
          });
    
          tearDown(() {
            reset(newsApi);
          });
    
          test('Get Articles', () async {
            // Arrange
            NewsService newsService = NewsService(newsApi: newsApi);
    
            final response = await newsApi.get('/top-headlines');
            print('Response');
            print(response.data);
    
            final articles = Articles.fromJson(response.data);
    
            print(articles);
    
            // Act
            await newsService.getArticlesByCategory(NewsCategory.business);
    
            // Assert
            verify(() => newsApi.get('/top-headlines',
                queryParameters: any(named: 'queryParameters'))).called(1);
          });
        });
      });
    }
    
    
    • Ali Akber
      Ali Akber about 2 years
      Were you able to fix this? I'm facing the same error when mocking a post request.
    • Ariel
      Ariel almost 2 years
      Yes, you've got to use http_mock_adapter: ^0.1.4 for mocking.