You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Mockito测试NewsAPI时遇类型错误:Null无法转为Future<List<Article>>

解决Mockito测试NewsAPI时的Null类型错误

错误详情

报错信息如下:

Testing started at 8:12 PM ...
D:\flutter\flutter_windows_3.3.10-stable\flutter\bin\flutter.bat --no-color test --machine --plain-name "testing http" test\unit_testing\provider_test.dart

test\unit_testing\provider_test.dart 12:7   MockClient.NewArticles
test\unit_testing\provider_test.dart 27:31  main.<fn>.<fn>

type 'Null' is not a subtype of type 'Future<List<Article>>'

问题根源

  1. 初始测试逻辑错误:Mock的GetApiData(MockClient)未设置NewArticles()的返回值,调用后默认返回null,但方法声明要求返回Future<List<Article>>,导致类型不匹配。
  2. 依赖未解耦:原ProviderData直接硬编码使用http.get,未提供注入点,无法替换为Mock客户端来模拟网络请求。
  3. 第二种写法混淆Mock对象:错误地尝试Mockclient.get,但实际测试代码中未正确关联到ProviderData的网络请求逻辑。

解决方案

步骤1:改造Provider,支持注入HTTP客户端

修改ProviderData,添加HTTP客户端的注入逻辑,让测试时可以传入Mock客户端:

import 'package:flutter/foundation.dart';
import 'dart:convert';
import 'package:http/http.dart'as http;
import 'package:news_app/Article.dart';

abstract class GetApiData {
  Future<List<Article>> NewArticles();
}

class ProviderData extends ChangeNotifier implements GetApiData{
  late Map<String, dynamic> json;
  final http.Client client;
  List<Article> _articles = [];

  // 构造函数注入客户端,默认使用真实的http.Client
  ProviderData({http.Client? client}) : client = client ?? http.Client();

  Future<List<Article>> NewArticles()async{
    final response = await client.get(Uri.parse('https://newsapi.org/v2/top-headlines?sources=techcrunch&apiKey=977b53e9e8624f64b649a80c04a656fe'));
    if (response.statusCode == 200){
      json = jsonDecode(response.body);
      List<dynamic> body = json['articles'];
      _articles = body.map((e) => Article.fromJson(e)).toList();
      notifyListeners();
      return _articles;
    } else {
      throw Exception('Failed to load articles: ${response.body}');
    }
  }
}

步骤2:编写正确的Mockito测试代码

Mockhttp.Client,模拟网络响应,测试ProviderData的实际逻辑:

import 'dart:convert';
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:news_app/Article.dart';
import 'package:news_app/Provider.dart';
import 'provider_test.mocks.dart';

// 生成MockClient,用于模拟http.Client
@GenerateMocks([http.Client])
void main(){
  late MockClient mockClient;

  setUp((){
    mockClient = MockClient();
  });

  group('testing http',() {
    test('returns list of articles on successful response', () async {
      // 构造符合NewsAPI格式的模拟响应
      final mockResponse = jsonEncode({
        'articles': [
          {
            "source":{"id":"techcrunch", "name": "TechCrunch"},
            "author":"Rebecca Bellan",
            "title":"Ford Q1 earnings rebound as trucks and fleet sales drive profits",
            "description":"Ford beat Wall Street revenue expectations of around 36 billion for the quarter with a total revenue of 41.5 billion, up 20% YoY.",
            "url":"https://techcrunch.com/2023/05/02/ford-q1-earnings-rebound-as-trucks-and-fleet-sales-drive-profits/",
            "urlToImage":"https://techcrunch.com/wp-content/uploads/2022/06/Ford-Europe-GettyImages.jpeg?resize=1200,800",
            "publishedAt":"2023-05-02T21:13:12Z",
            "content":"Ford reported its first quarter 2023 earnings Tuesday after the bell..."
          }
        ]
      });

      // 设置MockClient的get方法返回成功响应
      when(mockClient.get(Uri.parse('https://newsapi.org/v2/top-headlines?sources=techcrunch&apiKey=977b53e9e8624f64b649a80c04a656fe')))
          .thenAnswer((_) async => http.Response(mockResponse, 200));

      // 创建Provider实例并传入MockClient
      final provider = ProviderData(client: mockClient);
      final articles = await provider.NewArticles();

      // 断言结果符合预期
      expect(articles.length, 1);
      expect(articles.first.title, "Ford Q1 earnings rebound as trucks and fleet sales drive profits");
      // 验证MockClient的get方法被调用一次
      verify(mockClient.get(Uri.parse('https://newsapi.org/v2/top-headlines?sources=techcrunch&apiKey=977b53e9e8624f64b649a80c04a656fe'))).called(1);
    });

    test('throws exception on failed response', () async {
      // 设置MockClient返回失败响应
      when(mockClient.get(Uri.parse('https://newsapi.org/v2/top-headlines?sources=techcrunch&apiKey=977b53e9e8624f64b649a80c04a656fe')))
          .thenAnswer((_) async => http.Response('Error loading data', 404));

      final provider = ProviderData(client: mockClient);

      // 断言抛出异常
      expect(() => provider.NewArticles(), throwsException);
    });
  });
}

关键注意事项

  • 依赖注入是核心:必须通过构造函数注入http.Client,才能在测试中替换为Mock对象,避免真实网络请求。
  • Mock对象要匹配:测试时Mock的是http.Client而非GetApiData,因为我们要验证ProviderData的实际业务逻辑,而非Mock它自身的方法。
  • 响应格式要一致:模拟的JSON必须严格匹配NewsAPI的返回结构(外层包含articles数组),否则解析时会报错。
  • 必须设置Mock返回值:所有Mock的方法都要通过when(...).thenAnswer(...)指定返回值,否则默认返回null,引发类型不匹配错误。

内容的提问来源于stack exchange,提问作者Jaden Appiah

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 17:27:57