Mocking http.Client

Mocking http.Client is well documented, yet it didn't go as smoothly as expected.

I'm following the guide at https://docs.flutter.dev/cookbook/testing/unit/mocking#3-create-a-test-file-with-a-mock-httpclient.

My first change compared to the doc is that I did not import http as http, because I don't have any conflict. So my code looks like this:

import 'spell_check_service_test.mocks.dart';
import 'package:flashcards/language_service.dart';
import 'package:flashcards/spell_check_service.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';

@GenerateMocks([Client])
void main() {
  test('service', () async {
    final ko = Language.korean;
    final client = MockClient();
    final s = SpellCheckService(client);
    final mockResponse = Response("abcd", 200);
    when(client.get(any)).thenAnswer((_) {
      return Future.value(mockResponse);
    });

But when I ran the test, I got this error:

MissingStubError: 'get'
No stub was found which matches the arguments of this method call:
get(https://some_url, {headers: {}})
Add a stub for this method using Mockito's 'when' API, or generate the MockClient mock with the @GenerateNiceMocks annotation (see https://pub.dev/documentation/mockito/latest/annotations/MockSpec-class.html).

Looks like the usage of Mockito has changed and the Flutter team didn't update their docs.

Reading MockSpec-class, I can see why it stopped working:

To use the legacy behavior of returning null for unstubbed methods, use returnNullOnMissingStub: true.

I can also see that this property is also deprecated:

@Deprecated('Specify "missing stub" behavior with the ' '[onMissingStub] parameter.') bool returnNullOnMissingStub = false

But it doesn't matter. In my case I just want to generate a stub for get. The error tells me I should use the 'when' API, which I did...

I looked for some more docs and ended up on Mockito's guide at https://github.com/dart-lang/mockito/blob/master/NULL_SAFETY_README.md#solution-1-code-generation.

In this guide, they instruct us to use a new annotation:

To direct Mockito to generate mock classes, use the new @GenerateNiceMocks annotation, and import the generated mocks library.

It'd be nice if everyone kept their docs up to date.

Testing with headers

I was surprised to see that the response in my tests' client.get was null, since I explicitly stubbed the method to return some Response. It turns out I called it with custom headers: client.get([some url], headers: [some headers]). How do I stub get to catch calls with headers too?

So I tried this:

when(client.get(any, headers: any)).thenAnswer((_) {
  return Future.value(mockResponse);
});

And got this error:

Invalid argument(s): An argument matcher (like `any`) was either not used as an immediate argument to Symbol("get") (argument matchers can only be used as an argument for the very method being stubbed or verified), or was used as a named argument without the Mockito "named" API (Each argument matcher that is used as a named argument needs to specify the name of the argument it is being used in. For example: `when(obj.fn(x: anyNamed("x")))`).

So I modified it to:

when(client.get(any, headers: anyNamed('headers'))).thenAnswer((_) {
  return Future.value(mockResponse);
});

And it ran fine. Later I found that it was all well documented at https://pub.dev/packages/mockito#named-arguments.

Returning a different response based on the requested url

You basically read from the invocation details:

when(client.get(any, headers: anyNamed('headers')))
  .thenAnswer((invocation) {
  final Uri uri = invocation.positionalArguments.first;
  final body =
    uri.host.contains('google') ? GoogleSuggestions : NaverSuggestions;
  final mockResponse = Response(body, 200,
    // Make sure the Korean gets decoded correctly.
    headers: {'content-type': 'text/html; charset=utf-8'});
  return Future.value(mockResponse);
});

I have to specify the type of uri because positional arguments are dynamic.