TheSmartMonkey
TheSmartMonkey

Reputation: 1052

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

Upvotes: 0

Views: 833

Answers (2)

TheSmartMonkey
TheSmartMonkey

Reputation: 1052

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

Upvotes: 0

yshean
yshean

Reputation: 441

This is because you called a real method instead of calling a stub. To mock a stub, you can use the Mocktail package (I prefer this over mockito because it is easier to use with null safety) and create a class MockJsonPlaceholderService that extends Mock from the mocktail package.

Then make the JsonPlaceholderService as a constructor argument of MyApp (this is known as dependency injection, you may refer to this section of the article for more details).

So that in your test, after you have created an instance of MockJsonPlaceholderService, you may define a stub method with:

when(() => mockJsonPlaceholderService.getPlaceholder()).thenReturn(yourMockResponse);

Let me know if this helps.

Upvotes: 1

Related Questions