6

I have been working on a new API wrapper and don't want to be calling the API every time my unit tests run. So as described here, I'm mocking it.

I initially thought there was something wrong with the way I'm mocking it, but it seems the problem is elsewhere.

What I'm trying to accomplish is very simple. When my unit test run, I would like to return a value as if I had gone out to get the information from the external API I'm integrating with.

I initialise my class with http.Client as an optional parameter, so I can pass it in when the unit tests run as such:

SampleClass(String arg1, String arg2, [http.Client httpClient = null]) {
    this._arg1 = arg1;
    this._arg2 = arg2;
    _httpClient = (httpClient == null) ? http.Request : httpClient;
}

Future apiRequest(String resource, [Map<String, String> body]) {
    var url = buildBaseUrl(resource).toString();
    var request = new http.Request('POST', Uri.parse(url));
    request.bodyFields = body;
    return this._httpClient.send(request).then((response) => response.stream.bytesToString().then((value) => value.toString()));
}

In my unit test I have created the following mock class:

class HttpClientMock extends Mock implements http.Client {
  noSuchMethod(i) => super.noSuchMethod(i);
}

class HttpResponseMock extends Mock implements http.Response {
    noSuchMethod(i) => super.noSuchMethod(i);
}

And in my unit test to check the response I'm doing the following:

test("Send SMS errors with wrong account", () {
    var mockHttpClient = new HttpClientMock()
                             ..when(callsTo('send')).alwaysReturn(message401);
    var sample = new SampleClass(_arg1, _arg2, mockHttpClient);
    future = sample.apiRequest(...parameters here...).then((value) => value.toString());
    expect(future.then((value) => JSON.decode(value)), completion(equals(JSON.decode(message401))));
});

So, as you can see, I am trying to make it so calling send returns message401, which is just a JSON string.

This is not happening, since message401 is a string, and because my code tries to use it as a Future, I always get the error:

Top-level uncaught error: Class 'String' has no instance method 'then'.

I totally understand why I'm getting this error, but have no idea about how to go around it.

Any help appreciated.

4 Answers 4

13

The http package has a testing library with a MockClient already implemented for you.

Sign up to request clarification or add additional context in comments.

3 Comments

Can you guys post an example using MockClient please
@Frank The link in the answer has an example
Links are dead.
3

Try

.alwaysReturn(new Future.value(message401));

1 Comment

Your example absolutely worked, but I have marked Sean's answer as the correct one, since it offers a "native" way of accomplishing this. thank you so much though!
2

There is the nock package for that:

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

void main() {
  setUpAll(() {
    nock.init();
  });

  setUp(() {
    nock.cleanAll();
  });

  test("example", () async {
    final interceptor = nock("http://localhost/api").get("/users")
      ..reply(
        200,
        "result",
      );

    final response = await http.get("http://localhost/api/users");

    expect(interceptor.isDone, true);
    expect(response.statusCode, 200);
    expect(response.body, "result");
  });
}

It uses HttpOverrides, so you don't need to inject MockClient. Works both with dio and http packages.

1 Comment

Okay, but I have a problem with it. I'm trying to create several interceptors (because I have several requests) but only 1 works ...
1

A minimal example from the included tests for MockClient in the http package you can get from pub.dev

Add the http package to your pubspec.yaml file...

dependencies:
  http: ^0.12.2

In your unit test dart file...

import 'dart:convert';

import 'package:http/http.dart';
import 'package:http/testing.dart';
import 'package:test/test.dart';

void main() {

  test('handles a request', () async {
    var client = MockClient((request) async {
      return Response(json.encode(request.bodyFields), 200, request: request);
    }
    );

    var response = await client.post('http://example.com/foo', body: {'field1': 'value1'});

    expect(response.body, contains('value1'));
  });
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.