Dart-httpパッケージのテスト

Dartのhttpパッケージを使ったルーチンのテストについて。

サンプルみたいなものはMockitoを使用した例を以下のページで紹介されている。

Mock dependencies using Mockito
Use the Mockito package to mimic the behavior of services for testing.

これをMockitoを使わずにhttpパッケージだけでテストができる例を紹介する。

httpパッケージだけの例

import 'dart:convert';

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

class Album {
  final int userId;
  final int id;
  final String title;

  const Album({required this.userId, required this.id, required this.title});

  factory Album.fromJson(Map<String, dynamic> json) {
    return Album(
      userId: json['userId'],
      id: json['id'],
      title: json['title'],
    );
  }
}

Future<Album> fetchAlbum() async {
  final response = await http
      .get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1'));

  if (response.statusCode == 200) {
    // If the server did return a 200 OK response,
    // then parse the JSON.
    return Album.fromJson(jsonDecode(response.body));
  } else {
    // If the server did not return a 200 OK response,
    // then throw an exception.
    throw Exception('Failed to load album');
  }
}

void main() {
  group('fetchAlbum', () {
    test('returns an Album if the http call completes successfully', () async {
      final client = MockClient(
        (request) async {
          if (request.url.toString() !=
              "https://jsonplaceholder.typicode.com/albums/1") {
            return http.Response("", 404);
          }
          return http.Response('{"userId": 1, "id": 2, "title": "mock"}', 200,
              headers: {'content-type': 'text/html; charset=utf-8'});
        },
      );

      expect(
          await http.runWithClient(() {
            return fetchAlbum();
          }, () => client),
          isA<Album>());
    });

    test('throws an exception if the http call completes with an error', () {
      final client = MockClient((request) async {
        if (request.url.toString() !=
            "https://jsonplaceholder.typicode.com/albums/1") {
          return http.Response("", 404);
        }
        return http.Response('Not Found', 404,
            headers: {'content-type': 'text/html; charset=utf-8'});
      });

      expect(
          http.runWithClient(() {
            return fetchAlbum();
          }, () => client),
          throwsException);
    });
  });
}

Clientのモック関数

Mockitoで作成したClientのモックを使うのではなくhttpパッケージにすでに用意されているClientのモックであるMockClientを使っている。

Mockitoではwhenでレスポンスをする処理を入れているのだけど、MockClientはレスポンス関数をコンストラクタで渡す形になっている。

when単一のモックだけであれば、ほぼそのままレスポンス関数として定義できる。

複雑なモックの場合は、レスポンス関数についてもそれなりの制御が必要になる。

http.getのモック

http.get(もしくはpostなどのメソッド)を今回使っているのだけど、その処理は内部でZoneが保持しているClientのファクトリーもしくはClient本体のファクトリーで生成されたClientを使用している。

デフォルトはBrowserClientもしくはIOClientが作成されるようになっている。

このままだとテスト用に作成したモッククライアントが使用されずテストは失敗する。

そこでrunWithClientを使用し、Zoneを新たに作成しそのClientのファクトリーをモッククライアントを使用するようにすることで、テストで用意したモッククライアントがhttp.getで使用されるようにすることでテストがうまくいくようにした。

テスト時にrunWithClientを使ううえでの注意点は、runWithClientのbodyで渡される関数内でテストの実施判断であるexpectは使用しないようにするということ。
もしexpectが失敗しても、その例外は作成されたZone内の例外として処理されてしまい、テストが失敗した例外としては認識されない。

そこで、runWithClientのbodyから実行結果をリターンで返し、それをexpectで判断するようにしている。

これで、実行が失敗してもテストのエラーとして計測することができる。

コメント

タイトルとURLをコピーしました