Felipe Augusto
Felipe Augusto

Reputation: 8184

How to mock http request in flutter integration test?

I'm trying to do so using Mockito, this is my test:

import 'package:http/http.dart' as http;
import 'package:utgard/globals.dart' as globals;
import 'package:flutter_driver/flutter_driver.dart';
import 'package:test/test.dart';
import 'package:mockito/mockito.dart';

class MockClient extends Mock implements http.Client {}

void main() {
  group('Login flow', () {

    final SerializableFinder loginContinuePasswordButton =
        find.byValueKey('login_continue_password_button');

    FlutterDriver driver;

    setUpAll(() async {
      driver = await FlutterDriver.connect();
    });

    tearDownAll(() async {
      if (driver != null) {
        //await driver.close();
      }
    });


    test('login with correct password', () async {
      final client = MockClient();

      when(client.post('http://wwww.google.com'))
          .thenAnswer((_) async => http.Response('{"title": "Test"}', 200));

      globals.httpClient = client;

      await driver.enterText('000000');
      await driver.tap(loginContinuePasswordButton);
    });
  });
}

And this is my http request code:

Future<Map<String, dynamic>> post({
  RequestType requestType,
  Map<String, dynamic> body,
}) async {
  final http.Response response =
      await globals.httpClient.post('http://wwww.google.com');

  print(response);

  final Map<String, dynamic> finalResponse = buildResponse(response);

  _managerErrors(finalResponse);

  return finalResponse;
}

And here I have the global:

library utgard.globals;

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

http.Client httpClient = http.Client();

However I continue to receive http errors, that indicates to me that the http wasn't replaced by the mock.

Upvotes: 13

Views: 21529

Answers (4)

Isaac Lyman
Isaac Lyman

Reputation: 2201

There is not a way to mock services (including HTTP calls) in a Flutter integration test. I can't emphasize this enough: it is not possible.

(If you're writing a widget test or unit test, mocking is straightforward. You can write your own mocks or use mocktail or mockito.)

For the purposes of this answer, you're in an integration test if you're using IntegrationTestWidgetsFlutterBinding.ensureInitialized()—that is, if the test is running on a simulator or a physical device. In this case, the app code and the test code are running on different isolates, so nothing you do in your test code can affect what's happening in the app code and vice versa. They are walled off from each other.

Back in the days of flutter_driver, you could use a DataHandler to pass messages back and forth between your test and the app as described in this article. flutter_driver is obsolete now and there is no equivalent for the integration_test package; see this GitHub issue, which at present is still open.

The Flutter team's concept of integration tests is that they must mirror exactly what the app does when an end user is using them. Ergo, no mocking allowed. All HTTP requests are stubbed (returning HTTP 400) in widget/unit tests, but they work as normal in integration tests.

If you need repeatability in an integration test, you'll have to figure it out at the environment level, outside of Flutter.

Upvotes: -1

Jack&#39;
Jack&#39;

Reputation: 2526

We don't see the code you're testing, but it's unlikely that it is going to make this request :

client.post('http://wwww.google.com')

It is anyway a good practice to use mock json files, and you don't want to change the request anytime those mock files change.

I recommend you to use Mocktail instead of Mockito.

That way, you can simulate any call with any :

// Simulate any GET :
mockHttpClient.get(any()))

// Simulate any POST :
mockHttpClient.post(any()))

Here's the complete solution :

class MockClient extends Mock implements http.Client {}
class FakeUri extends Fake implements Uri {}

void main() {

  setUp(() {
    registerFallbackValue(FakeUri()); // Required by Mocktail
  });
  tearDown(() {});

  MockHttpClient mockHttpClient = MockHttpClient();
  group('Login flow', () {
    test('login with correct password', () async {
      when(() => mockHttpClient.get(any())).thenAnswer(((_) async {
        return Response(mockWeatherResponse, 200);
      }));
    
    // Call the functions you need to test here

    }
  }
}

Upvotes: 3

Felipe Augusto
Felipe Augusto

Reputation: 8184

The solution I found was to define the mock in test_driver/app.dart and call the runApp function after that:

import 'package:flutter/widgets.dart';
import 'package:flutter_driver/driver_extension.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:utgard/business/config/globals.dart';
import 'package:utgard/main.dart' as app;

class MockClient extends Mock implements http.Client {}

void main() {
  enableFlutterDriverExtension();

  final MockClient client = MockClient();
  // make your mocks here
  httpClient = client;

  runApp(app.MyApp());
}

Upvotes: 5

TruongSinh
TruongSinh

Reputation: 4866

Instead of

      when(client.post('http://wwww.google.com'))
          .thenAnswer((_) async => http.Response('{"title": "Test"}', 200));

try any and then assert it later

        when(
          mockHttpClient.send(any),
        ).thenAnswer((_) async => http.Response('{"title": "Test"}', 200));
// ...
        final String capt = verify(client.send(captureAny)).captured;
        expect(capt, 'http://wwww.google.com');

There's a small chance the call param is not exactly what you mock, so go with any is safer.

Upvotes: 2

Related Questions