Search code examples
flutterflutter-test

How to spyon/stub an http request in a flutter widget test


I'm trying to give a value to JsonPlaceholderService().getPlaceholder() when it's called on button click in the widget test but it fails with

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);
  });
}

All this code is available in this repo : https://github.com/TheSmartMonkey/flutter-http-widget-test


Solution

  • First init your service in your widget constructor then it could be mocked

    Second mock your service

    main.dart

    void main() {
      runApp(MyApp(client: JsonPlaceholderService()));
    }
    

    home.dart

    class MyHomePage extends StatefulWidget {
      final String title;
      final JsonPlaceholderService client;
    
      const MyHomePage({
        Key? key,
        required this.title,
        required this.client,
      }) : super(key: key);import 'package:flutter/material.dart';
    import 'package:fluttertest/json_placeholder_service.dart';
    
    class MyHomePage extends StatefulWidget {
      final String title;
      final JsonPlaceholderService client;
    
      const MyHomePage({
        Key? key,
        required this.title,
        required this.client,
      }) : super(key: key);
    
      @override
      State<MyHomePage> createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      String text = '';
    
      Future<void> _onPressed() async {
        final response = await widget.client.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),
              ],
            ),
          ),
        );
      }
    }
    
    

    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:mocktail/mocktail.dart';
    
    class MockJsonPlaceholderService extends Mock
        implements JsonPlaceholderService {}
    
    void main() {
      final mockJsonPlaceholderService = MockJsonPlaceholderService();
    
      testWidgets('Should display api text on button click',
          (WidgetTester tester) async {
        // Given
        // Build our app and trigger a frame.
        await tester.pumpWidget(
          MyApp(client: mockJsonPlaceholderService),
        );
    
        // When
        // Stub http request
        final json = {
          "userId": 1,
          "id": 1,
          "title": "delectus aut autem",
          "completed": false
        };
        final response = JsonPlaceholderModel.fromJson(json);
        when(() => mockJsonPlaceholderService.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));
    
        // Then
        verify(() => mockJsonPlaceholderService.getPlaceholder()).called(1);
        expect(find.text('delectus aut autem'), findsOneWidget);
      });
    }
    

    article : https://gist.github.com/brianegan/414f6b369c534a0e5f20bff377823414

    All this code is available in this repo : https://github.com/TheSmartMonkey/flutter-http-widget-test