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

Flutter Widget测试中Stub HTTP请求报错,求解决方法

Flutter Widget测试Mock服务方法失败的解决办法

问题现象

在Flutter Widget测试中,尝试为按钮点击时调用的JsonPlaceholderService().getPlaceholder()指定返回值,测试失败并抛出如下错误:

StateError (Bad state: No method stub was called from within when().
Was a real method called, or perhaps an extension method?)

相关代码

home.dart

import 'package:flutter/material.dart';
import 'package:fluttertest/json_placeholder_service.dart';

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  String text = '';

  Future<void> _onPressed() async {
    final response = await JsonPlaceholderService().getPlaceholder();
    setState(() {
      text = response.title;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              child: const Text('Get text'),
              onPressed: () => _onPressed(),
            ),
            const SizedBox(height: 40),
            Text(text),
          ],
        ),
      ),
    );
  }
}

json_placeholder_model.dart

class JsonPlaceholderModel {
  int id;
  int userId;
  String title;
  bool completed;

  JsonPlaceholderModel({
    required this.id,
    required this.userId,
    required this.title,
    required this.completed,
  });

  Map<String, dynamic> toJson() => {
        'id': id,
        'userId': userId,
        'title': title,
        'completed': completed,
      };

  JsonPlaceholderModel.fromJson(dynamic json)
      : id = json['id'],
        userId = json['userId'],
        title = json['title'],
        completed = json['completed'];
}

json_placeholder_service.dart

import 'dart:convert';

import 'package:fluttertest/json_placeholder_model.dart';
import 'package:http/http.dart' as http;

class JsonPlaceholderService {
  Future<JsonPlaceholderModel> getPlaceholder() async {
    const url = 'https://jsonplaceholder.typicode.com/todos/1';
    final response = await http.get(Uri.parse(url));
    final data = jsonDecode(const Utf8Decoder().convert(response.bodyBytes));
    final JsonPlaceholderModel json = JsonPlaceholderModel.fromJson(data);
    return json;
  }
}

widget_test.dart(原错误代码)

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:fluttertest/json_placeholder_model.dart';
import 'package:fluttertest/json_placeholder_service.dart';
import 'package:fluttertest/main.dart';
import 'package:mockito/mockito.dart';

void main() {
  testWidgets('Should display api text on button click',
      (WidgetTester tester) async {
    // Build our app and trigger a frame.
    await tester.pumpWidget(const MyApp());

    // Stub http request
    final json = {
      "userId": 1,
      "id": 1,
      "title": "delectus aut autem",
      "completed": false
    };
    final response = JsonPlaceholderModel.fromJson(json);
    // Methode 1 -> says to use methode 2
    when(JsonPlaceholderService().getPlaceholder())
        .thenReturn(Future.value(response));
    // Methode 2
    when(JsonPlaceholderService().getPlaceholder())
        .thenAnswer((_) async => response);

    // Click the button
    final button = find.byType(ElevatedButton);
    expect(button, findsOneWidget);
    await tester.tap(button);
    await tester.pumpAndSettle();
    await tester.pump(const Duration(seconds: 2));

    // The text is displayed
    expect(find.text('delectus aut autem'), findsOneWidget);
  });
}

问题原因

Mockito只能对继承自Mock类的实例进行方法Stub,原测试中每次调用JsonPlaceholderService()都会创建新的真实类实例,导致Stub规则无法绑定到实际运行时使用的实例上,进而抛出错误。

解决方案

1. 创建Mock服务类

手动创建继承自Mock的Mock类:

class MockJsonPlaceholderService extends Mock implements JsonPlaceholderService {}

也可使用Mockito代码生成工具,添加@GenerateMocks([JsonPlaceholderService])注解后运行dart run build_runner build自动生成。

2. 改造Widget实现依赖注入

修改MyHomePage,通过构造函数注入服务实例,避免在方法内直接创建真实服务:

class MyHomePage extends StatefulWidget {
  const MyHomePage({
    Key? key,
    required this.title,
    required this.service, // 添加服务注入参数
  }) : super(key: key);

  final String title;
  final JsonPlaceholderService service;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  String text = '';

  Future<void> _onPressed() async {
    // 使用注入的服务实例
    final response = await widget.service.getPlaceholder();
    setState(() {
      text = response.title;
    });
  }

  // build方法保持不变
}

同时修改MyApp,在正式环境中传入真实服务:

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: MyHomePage(
        title: 'Flutter Demo Home Page',
        service: JsonPlaceholderService(), // 传入真实服务
      ),
    );
  }
}

3. 修改测试代码使用Mock实例

在测试中创建Mock服务实例,绑定Stub规则后注入到Widget中:

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:fluttertest/json_placeholder_model.dart';
import 'package:fluttertest/json_placeholder_service.dart';
import 'package:fluttertest/main.dart';
import 'package:mockito/mockito.dart';

// 定义Mock类
class MockJsonPlaceholderService extends Mock implements JsonPlaceholderService {}

void main() {
  testWidgets('点击按钮后显示API返回文本', (WidgetTester tester) async {
    // 创建Mock服务实例
    final mockService = MockJsonPlaceholderService();
    
    // 准备模拟返回数据
    final mockData = {
      "userId": 1,
      "id": 1,
      "title": "delectus aut autem",
      "completed": false
    };
    final mockResponse = JsonPlaceholderModel.fromJson(mockData);
    
    // 绑定Stub规则:调用getPlaceholder时返回模拟数据
    when(mockService.getPlaceholder()).thenAnswer((_) async => mockResponse);

    // 注入Mock服务并构建Widget
    await tester.pumpWidget(MaterialApp(
      home: MyHomePage(title: 'Test', service: mockService),
    ));

    // 执行按钮点击操作
    await tester.tap(find.byType(ElevatedButton));
    await tester.pumpAndSettle();

    // 验证文本是否正确显示
    expect(find.text('delectus aut autem'), findsOneWidget);
  });
}

4. 依赖检查

确保pubspec.yaml中已添加Mockito依赖:

dev_dependencies:
  flutter_test:
    sdk: flutter
  mockito: ^5.4.0

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 00:24:32