🫒

mockの注入で、文字化けが起きてテストに失敗する?

2024/02/23に公開

対象者

  • Dartのテストコードに興味がある人
  • httpでmockを使ってる人

やること/やらないこと

やること:

  • httpmockitoを使って、json-serverにモックの注入をやってみます。
  • json-serverにモックを注入する。
  • 今回文字化けが起きたので防止する

このエラーの対策をする

jboy422@Jboy422 unit_test % flutter test todo_api.test.dart
Changing current working directory to:
jboy422@Jboy422 unit_test % flutter test todo_api.test.dart
Changing current working directory to:
/Users/MY_PJ/flutter_pj/performin_side_effects
00:00 +0: ... POST API 関数が正常にPOSTリクエストを送信し、Tod00:00 +0 -1: todoAPI -  POS API function POST API 関数が正常にPOSTリクエストを送信し、Todoを返すことを確認する。 [E]
  Expected: 'モックを注入しちゃうもんね??????'
    Actual: 'ã¢ãã¯ã注å
¥ãã¡ããããã­??????'
     Which: is different.
            Expected: モックを注入しちゃう ...
jboy422@Jboy422 unit_test % flutter test todo_api.test.dart
Changing current working directory to:
/Users/MY_PJ/flutter_pj/performin_side_effects
00:00 +0: ... POST API 関数が正常にPOSTリクエストを送信し、Tod00:00 +0 -1: todoAPI -  POS API function POST API 関数が正常にPOSTリクエストを送信し、Todoを返すことを確認する。 [E]
jboy422@Jboy422 unit_test % flutter test todo_api.test.dart
Changing current working directory to:
/Users/MY_PJ/flutter_pj/performin_side_effects
00:00 +0: ...e_effects/test/unit_test/todo_api.test.dart     test/unit_test/todo_api.test.dart:19:23: Error: No named
parameter with the name 'client'.
    todoAPI = TodoAPI(client: mockClient); // モックの http.Client
    を注入
                      ^^^^^^
lib/infra/api/todo_api.dart:7:7: Context: The class 'TodoAPI'
jboy422@Jboy422 unit_test % flutter test todo_api.test.dart
Changing current working directory to:
/Users/MY_PJ/flutter_pj/performin_side_effects

このコードを使えば解決した。utf8を指定して、エンコードするみたいです。

final result = Todo.fromJson(jsonDecode(utf8.decode(response.bodyBytes)));

やらないこと:

  • 今回は、モックの注入を紹介するだけなので、コードについては詳しく説明しないです。
  • Dartやモックについて知識がある人が対象です。

プロジェクトの説明

過去に作成した記事で使用したソースコードを使ってテストコードを書いてみようと思います。
https://zenn.dev/joo_hashi/articles/20d8dcb53767dc

こちらのサンプルを参考にしてみてください。
https://github.com/sakurakotubaki/performin_side_effects/tree/main/test/unit_test

freezedでモデルを作成する。これに合わせて、bodyを作る。

import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:flutter/foundation.dart';

part 'todo.freezed.dart';
part 'todo.g.dart';

// 公式の引数に合わせてモデルを定義

class Todo with _$Todo {
  const factory Todo({
    ('') String description,
    (false) bool completed,
  }) = _Todo;

  factory Todo.fromJson(Map<String, Object?> json)
      => _$TodoFromJson(json);
}

今回はテスト用にAPIにPOSTするクラスを作成いたしました。json-serverを起動した状態で使用します。

import 'dart:convert';

import 'package:flutter/foundation.dart';
import 'package:performin_side_effects/domain/todo.dart';
import 'package:http/http.dart' as http;

class TodoAPI {

  Future<Todo> addTodo(Todo todo) async {
    try {
 final response = await http.post(
        Uri.http('localhost:3000', '/todo'),
        headers: {'Content-Type': 'application/json'},
        body: jsonEncode(todo.toJson()),
      );
      switch (response.statusCode) {
        case 200:
        case 201:
        final result = Todo.fromJson(jsonDecode(utf8.decode(response.bodyBytes)));
          return result;
        default:
          throw Exception(response.reasonPhrase);
      }
    } on Exception catch (e) {
      debugPrint('api call error: $e');
      rethrow;
    }
  }
}

todo_api.test.dartを作成して、テストを作成して、build_runnerのコマンドを実行すると、モック用のファイルが自動生成されます。ターミナルで、テストコードがあるディレクトリに移動して、テスト用のコマンドを実行すると、テストコードを実行できます。

flutter pub run build_runner watch --delete-conflicting-outputs

import 'dart:convert';

import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:performin_side_effects/domain/todo.dart';
import 'package:performin_side_effects/infra/api/todo_api.dart';
import 'package:http/http.dart' as http;

import 'todo_api.test.mocks.dart';

([http.Client])
void main() {
  late TodoAPI todoAPI;
  late MockClient mockClient;

  setUp(() {
    mockClient = MockClient();
    todoAPI = TodoAPI(); // モックの http.Client を注入
  });

  group('todoAPI - ', () {
    group('POS API function', () {
      test(
        'POST API 関数が正常にPOSTリクエストを送信し、Todoを返すことを確認する。',
        () async {
          final todo = Todo(completed: true, description: 'モックを注入しちゃうもんね!');
          // Arrange
          when(mockClient.post(
            any,
            headers: anyNamed('headers'),
            body: anyNamed('body'),
          )).thenAnswer((_) async => http.Response(
              '{"description": "モックを注入しちゃうもんね!", "completed": true}', 200));
          // Act
          final result = await todoAPI.addTodo(todo);
          expect(result, isA<Todo>());
          // expect(result, equals(todo));
          expect(result.description, equals('モックを注入しちゃうもんね!')); // 修正
          expect(result.completed, equals(true)); // 修正
        },
      );
    });
  });
}

テストを実行する:

flutter test todo_api.test.dart

このようの結果が返ってくればOK!

jboy422@Jboy422 unit_test % pwd
/Users/MY_PJ/flutter_pj/performin_side_effects/test/unit_test
jboy422@Jboy422 unit_test % flutter test todo_api.test.dart
Changing current working directory to:
/Users/MY_PJ/flutter_pj/performin_side_effects
00:01 +0: ... POST API 関数が正常にPOSTリクエストを送信し、Tod00:01 +1: ... POST API 関数が正常にPOSTリクエストを送信し、Tod00:01 +1: All tests passed!        
jboy422@Jboy422 unit_test % 

感想

HTTP POSTしたときに、status codeが200以外に201もcase文に入れていないと、テストをしたときに例外処理が発生していました。

こちらのサイトに詳しく解説されているので読んでみてください。
https://developer.mozilla.org/ja/docs/Web/HTTP/Status/200
HTTP 200 OK はリクエストが成功した場合に返すレスポンスコード。200 のレスポンスはデフォルトでキャッシュしてよい。

成功したという意味はリクエストのメソッドによって異なる:

GET: リソースがフェッチされメッセージのボディ部で返送された。
HEAD: エンティティヘッダがボディ部で返送された。
POST: 実行された結果が記載されたリソースがボディ部で返送された。
TRACE: メッセージのボディ部にサーバーで受信したリクエストメッセージを含んでいる。
PUT や DELETE の成功結果は 200 OK ではなく、 204 No Content (や、リソースの初回アップロードによる作成の場合は 201 Created )である場合もある。

https://developer.mozilla.org/ja/docs/Web/HTTP/Status/201
HTTP の 201 Created 成功ステータスレスポンスコードは、リクエストが成功してリソースの作成が完了したことを表します。レスポンスが返される前に、新たなリソースが作成され、レスポンスメッセージの本文にて新しいリソースが返されます。その位置はリクエスト URL、または Location ヘッダーの内容となります。

このステータスコードの一般的な使用例は、 POST リクエストの結果です。

Discussion