使用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>>'
问题根源
- 初始测试逻辑错误:Mock的
GetApiData(MockClient)未设置NewArticles()的返回值,调用后默认返回null,但方法声明要求返回Future<List<Article>>,导致类型不匹配。 - 依赖未解耦:原
ProviderData直接硬编码使用http.get,未提供注入点,无法替换为Mock客户端来模拟网络请求。 - 第二种写法混淆Mock对象:错误地尝试Mock
client.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
相关产品推荐
相关产品推荐

