mockの注入で、文字化けが起きてテストに失敗する?
対象者
- Dartのテストコードに興味がある人
- httpでmockを使ってる人
やること/やらないこと
やること:
-
http
とmockito
を使って、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やモックについて知識がある人が対象です。
プロジェクトの説明
過去に作成した記事で使用したソースコードを使ってテストコードを書いてみようと思います。
こちらのサンプルを参考にしてみてください。
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文に入れていないと、テストをしたときに例外処理が発生していました。
こちらのサイトに詳しく解説されているので読んでみてください。
HTTP 200 OK はリクエストが成功した場合に返すレスポンスコード。200 のレスポンスはデフォルトでキャッシュしてよい。成功したという意味はリクエストのメソッドによって異なる:
GET: リソースがフェッチされメッセージのボディ部で返送された。
HEAD: エンティティヘッダがボディ部で返送された。
POST: 実行された結果が記載されたリソースがボディ部で返送された。
TRACE: メッセージのボディ部にサーバーで受信したリクエストメッセージを含んでいる。
PUT や DELETE の成功結果は 200 OK ではなく、 204 No Content (や、リソースの初回アップロードによる作成の場合は 201 Created )である場合もある。
HTTP の 201 Created 成功ステータスレスポンスコードは、リクエストが成功してリソースの作成が完了したことを表します。レスポンスが返される前に、新たなリソースが作成され、レスポンスメッセージの本文にて新しいリソースが返されます。その位置はリクエスト URL、または Location ヘッダーの内容となります。
このステータスコードの一般的な使用例は、 POST リクエストの結果です。
Discussion