12
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

flutter_testの導入と書き方をざっくりとまとめてみた

Posted at

Testing Flutter Appを写経しました。
この記事では、後述するUnit Testについてまとめます。

Flutterの自動テスト

Flutterでの自動テストは、以下のように分類できます。

  • Unit Test: 関数やメソッド、クラスのロジックが正しいか検証します。
  • Widget Test: Widgetが期待通りに動作するか、期待通りのUIを実現できているか検証します。
  • Integration Test: アプリそのものや機能の大部分をテストします。Widgetやサービスを結合させて動作した時、期待通りに動作するか検証します

Unit Testはコストが低く、高速にテストできます。

Unit Test

ここでは、flutter_testを使ったテストの書き方を説明します。
Unit Testの効果的な書き方や考え方は別な記事を参考にしましょう。

flutter_testパッケージを導入する

flutter_testを導入するため、以下のようにpubspec.yamlに追記します。

dev_dependencies:
  flutter_test:
    sdk: flutter

テストを書いてみる

今回はCounterというクラスにテストを書きます。
lib/counter.dartを作成しましょう。

class Counter {
  int value = 0;
  void increment() => value++;
  void decrement() => value--;
}

test/counter_test.dartを追加し、テスト用のコードを書きます。

test()でテストケースを定義し、expect()で期待通りの結果が得られているか検証します。

import 'package:flutter_test/flutter_test.dart';
import 'package:unit_testing/counter.dart';

void main() {
  // group()でテストをグルーピングできる
  group('Counter', () {
    test('Counterの初期値は0', () {
      expect(Counter().value, 0);
    });

    test('increment()を呼び出すと1増える', () {
      final counter = Counter();
      counter.increment();
      expect(counter.value, 1);
    });

    test('decrement()を呼び出すと1減る', () {
      final counter = Counter();
      counter.decrement();
      expect(counter.value, -1);
    });
  });
}

このサンプルでは簡単な例ですが、他にも非同期のテストやMatcherを使った検証もできます。
リファレンスを読みましょう。

Mockitoの導入

テスト対象の振る舞いが別なクラスに依存している場合、モックライブラリのMockitoを使ってモックしましょう。

今回はhttpで通信した結果をモックしてみます。
例によってpubspec.yamlに追記します。

dependencies:
  flutter:
    sdk: flutter
  http:

dev_dependencies:
  flutter_test:
    sdk: flutter
  mockito:

lib/main.dartに、こんな感じでHTTPで通信してJSONをパースする処理を書きます。

import 'dart:async';
import 'dart:convert';

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

class Post {
  dynamic data;
  Post.fromJson(this.data);
}

Future<Post> fetchPost(http.Client client) async {
  final response =
      await client.get('https://jsonplaceholder.typicode.com/posts/1');

  if (response.statusCode == 200) {
    return Post.fromJson(json.decode(response.body));
  } else {
    throw Exception('Failed to load post');
  }
}

今回のクラスをテストするには、ネットワーク環境を準備したり通信先のサーバーを準備したりサーバーが狙い通りに動作するように準備したりと、準備がとても大変です。

なので、Mockitoを使ってサーバーとの通信が成功したように振る舞わせてテストしましょう。

import 'package:flutter_test/flutter_test.dart';
import 'package:first_mockito/main.dart';
import 'package:http/http.dart' as http;
import 'package:mockito/mockito.dart';

// モック対象のクラスを宣言します。
class MockClient extends Mock implements http.Client {}

void main() {
  group('fetchPost', () {
    test('returns a Post if the http call completes successfully', () async {
      // モックを生成します。
      final client = MockClient();
      // client.get()を呼び出した時に、どういうResponseを返すかモックします。
      // このテストでは200を返すケースをモックしてみます。
      when(client.get('https://jsonplaceholder.typicode.com/posts/1'))
          .thenAnswer((_) async => http.Response('{"title" : "Test"}', 200));
      expect(isInstanceOf<Post>(fetchPost(client)), isTrue);
    });

    test('throws an exception if the http call completes with an error', () {
      final client = MockClient();

      // このテストでは404を返すケースをモックしてみます。
      when(client.get('https://jsonplaceholder.typicode.com/posts/1'))
          .thenAnswer((_) async => http.Response('Not Found', 404));

      expect(fetchPost(client), throwsException);
    });
  });
}

Mockitoはモックだけでなく、スタブ・Fake(Partial Mock)の使い方も可能です。
公式のドキュメントを読んでみましょう。

12
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?