【问题标题】:Flutter - type 'Null' is not a subtype of type 'Future<Response<dynamic>>' when mocking Dio's get methodFlutter - 模拟 Dio 的 get 方法时,“Null”类型不是“Future<Response<dynamic>>”类型的子类型
【发布时间】:2022-06-23 11:45:18
【问题描述】:

我试图模拟 Dio 的 get 方法。根据我的测试,模拟运行良好。但是,在测试中调用时类型“Null”不是类型“Future&lt;Response&lt;dynamic&gt;&gt;”的子类型

我也在测试期间致电newsApi.get('/top-headlines')。而且,我可以向它保证,模拟返回数据正常。但不知什么原因,NewsService 内部的调用是给null。你能指导我解决这个问题吗?

文件名: 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;
    });
  }
}

文件名: 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);
      });
    });
  });
}

【问题讨论】:

  • 你能解决这个问题吗?在模拟 post 请求时,我遇到了同样的错误。
  • 是的,你必须使用 http_mock_adapter: ^0.1.4 来模拟。

标签: flutter dart mocking typeerror


【解决方案1】:

我终于找到了一个可行的解决方案。

Dio 似乎需要一个单独的模拟库来使用适配器模拟自己。要使用的库是http_mock_adapter: ^0.1.4,下面是它的链接。

链接: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)));
    });
  });
}

【讨论】:

    猜你喜欢
    • 2023-03-30
    • 2020-10-13
    • 2021-02-10
    • 1970-01-01
    • 2019-08-18
    • 2020-12-09
    • 2019-12-05
    • 2021-09-18
    • 1970-01-01
    相关资源
    最近更新 更多