Thumbnail - Belajar Melakukan Testing Di Flutter

Belajar Melakukan Testing Di Flutter

Kang Admin Avatar

Oleh

Pada

Kali ini saya akan membagikan cara melakukan testing di flutter dengan mudah, baik itu unit testing, widget testing dan integrated testing sehingga pada akhirnya diharapkan bisa mempercepat proses dalam membagun sebuah aplikasi. Kita bisa melakukan 3 jenis test pada flutter mulai dari unit test, widget test dan integrated test. Ketiga jenis test ini memiliki beberapa perbedaan dan berbandingan antara satu dan yang lainnya sebagai tabel berikut:

tabel performa test

Kenapa Harus Testing Kan Ada Live Reload

Saya sempat berpikir waktu dulu menggunakan flutter, kenapa harus buat testing yang menambah untuk menulis kode lagi, padahal flutter memiliki livereload yang otomatis merefresh widget ketika kode pada baris tertentu telah berubah. Ada beberapa alasan kenapa testing itu perlu dilakukan diantaranya sebagai berikut:

  • Menjaga struktur kode
  • Memastikan module, fungsi berjalan sebagai mana mestinya
  • Live reload hanya reload atau memanggil build pada widget
  • Mengurai waktu dalam pembangunan aplikasi

Untuk memahami lebih medalam bisa membaca terlebih dahulu dokumentasi yang disediakan oleh flutter team di flutter test documentation;

Penerapan Unit Tests

Unit testing ini ditujukan untuk menguji semua fungsi dasar yang ada pada aplikasi berjalan dengan baik serta untuk menguji bagaimana pengaruh sebuah fungsi satu dengan yang lainnya dan semua kemungkinan yang bisa ditimbulkan dari setiap input dan output pada fungsi tadi. Contohnya misalkan membuat aplikasi ramalan cuaca yang mengakses data perkiraan cuaca dari web api, maka diperlukan sebuah unit test yang dapat menghandle semua kemungkinan response yang diberikan oleh server dengan tujuan tidak terjadi crass saat aplikasi di jalankan atau runtime error. Berikut contoh penerapan unit test untuk melaukan uji coba terhadap sebuah web api untuk melaukan cek terhadap kode resi.


List<int> nilai = <int>[19, 20, 31, 13];

// test apakah nilai memiliki nilai 20
expect(nilai, contains(20));
// test fungsi apakah akan terjadi error `actual` bisa berupa Function, Future
expect(() => assert(nilai.first > 20), throwsA((e) => e is AssertionError));

Pada Kode di bawah ini bertujuan untuk membuat slug (linkabe) dari sebuah text, saya melaukan uji coba beberapa kemungkinan dari text yang dimasukan

String sluggify(String text) {
  return text
    .toLowerCase()
    .trim()
    .replaceAll(RegExp(r'(&:amp;| & )'), '-and-')
    .replaceAll(RegExp(r'&(.+?);'), '')
    .replaceAll(RegExp(r'[\s\W-]+'), '-');
}

expect(sluggify('Test dart & flutter'), equals('test-dart-and-flutter'));
expect(sluggify('Test dart &:copy; flutter'), equals('test-dart-flutter'));

Test Class Menggunakan Mockinto

Mockinto merupkan sebuah library yang membantu melakukan simulasi “mimic” pada sebuah fungsi yang terdapat pada sebuah class, itu karena test harus konstant ini berarti situasi bisa terkendali. Misalkan pada aplikasi Cuaca yang menggunakan module http untuk mengakses WEB API di internet, jadi test akan tergantung dari hasil web api bisa saja saat melaukukan sebuah request ada situasi yang tidak terkendali seperti request yang di tolak misalkan. Nah untuk mengatasi hal ini bisa melakukan mock terhadapt api yang kita akses dari pihak ketiga. Berikut hal - hal yang bisa digunakan untuk mockinto:

  • Melakuan simulasi untuk field dan method pada sebuah class
  • Menguji apakah sebuah fungsi telah terpanggil atau tidak
  • Reset Mock Object bisanya pada setUp atau tearDown

Mockinto tidak bisa melaukan mocking pada class yang menggunakan annotation @immutable ini berarti class seperti widget tidak bisa di mock menggunakan mockinto. Berikut contoh penggunaan mockinto untuk melaukan testing pada sebuah class yang menggunakan database.


Untuk diatas saya melaukan uji bagaimana menghadle crud data pada database mysql.

Cara Test Future dan Stream

Dart memudahkan dalam melaukan testing pada class Future dan Stream dengan mengguankan beberapa fungsi yang telah disediakan seperti expectAsync, expectLater, dan masih banyak lainnya. Jika perhatikan semua test serta pada setUpAll, setUp, tearDown, dan tearDownAll adalah fungsi yang async atau dalam kata lainnya bisa menggunakan keyword await dan async. Selain itu expect bisa di taruh di dalam fungsi listen pada Stream class atau then pada class Future dan test akan tetap berjalan normal.

List<int> nilai = <int>[19, 20, 20, 13];

Stream<int> angka = Stream<int>.fromIterable(nilai).distinct();
// karena sebelumnya telah melaukan emit 20 maka akan di skip
expect(angka, emitsInOrder([19, 20, 13]));

Contoh asyc await pada fungsi test:

main(){
  test('async function simple', () async {

  });
}

Misalnya ingin melaukan uji Stream apakah stream memiliki order yang sesuai dengan yang diinginkan. maka bisa dilakukan test sebagai berikut:

List<int> nilai = <int>[19, 20, 20, 13];

Stream<int> angka = Stream<int>.fromIterable(nilai).distinct();
// karena sebelumnya telah melaukan emit 20 maka akan di skip
expect(angka, emitsInOrder([19, 20, 13]));

Dapat juga menggunakan quiver seperti berikut:

//Todo fixme
test('should filter after the user stops typing for 500ms', () async {
  new FakeAsync().run((time) {
    final service = new MockService();
    final model = new HomePageModel(service);

    when(service.getWeatherEntriesForCity(any))
        .thenAnswer((_) => new Future.sync(() => <WeatherEntry>[]));

    model.textChangedCommand('A');

    // tuggu sampai 1000 ms baru eksekusi line berikutnya
    time.elapse(new Duration(milliseconds: 1000));

    // sebuah fungsi dari mockinto apakah menjalankan sebuah fungsi
    verify(service.getWeatherEntriesForCity('A'));
  });
});

Perlu diketahui test memiliki batas waktu (batas test dijalankan) jika batas test telah terlampaui maka test akan gagal dan akan berlanjut ke test selanjutnya jika ada.

Widget Tests

Widget testing berbeda dengan unit testing selain berinteraksi dengan flutter enggine, penulisan dan bentuk fungsinya juga berbeda. Untuk Widget Testing ini hal yang harus di ketahui adalah test state idle, pumpWidget, pump dan pumpAndSettle. idle adalah sebuah Future yang menunggu sampai semua microtask telah selesai di jalankan, pump akan mentrigger agar flutter enggine menjalankan 1 frame hal ini biasa dilakukan untuk menguji state pada statefull widget, dan pumpAndSettle ini menjalanakn semua frame yang ada dalam daftar tunggu sampai tidak ada antrian lagi, pumpWiget ini adalah widget yang akan di test. Pada pumpAndSettle ini tidak bisa diggunakan pada widget yang memiliki animasi yang infinite loop karena akan ada infinite frame schedule. Berikut contoh widget test untuk melaukan uji coba apakah pada saat TextField di tekan akan memangil dialog Date Picker dan akan merubah value pada TextField sesuai dengan pilihan pada dialog.


Penjelasan

Menghandle Network Request

Karena widget test tidak menggunakan device maka ada beberapa widget yang tidak bisa di gunakan seperti Image.network yang tidak bisa digunakan. Untuk mengatasi ini kita perlu melaukan mocking pada http client pada package dart:io seperti berikut:

import 'dart:io';

import 'package:transparent_image/transparent_image.dart';

HttpOverrides.global = TestHttpOverrides();

class TestHttpOverrides extends io.HttpOverrides {
  @override
  io.HttpClient createHttpClient(io.SecurityContext context) {
    return createMockImageHttpClient(context);
  }
}

// Returns a mock HTTP client that responds with an image to all requests.
MockHttpClient createMockImageHttpClient(SecurityContext _) {
  final MockHttpClient client = new MockHttpClient();
  final MockHttpClientRequest request = new MockHttpClientRequest();
  final MockHttpClientResponse response = new MockHttpClientResponse();
  final MockHttpHeaders headers = new MockHttpHeaders();

  when(client.getUrl(any)).thenAnswer((_) => new Future<HttpClientRequest>.value(request));
  when(request.headers).thenReturn(headers);
  when(request.close()).thenAnswer((_) => new Future<HttpClientResponse>.value(response));
  when(response.contentLength).thenReturn(kTransparentImage.length);
  when(response.statusCode).thenReturn(HttpStatus.ok);
  when(response.listen(any)).thenAnswer((Invocation invocation) {
    final void Function(List<int>) onData = invocation.positionalArguments[0];
    final void Function() onDone = invocation.namedArguments[#onDone];
    final void Function(Object, [StackTrace]) onError = invocation.namedArguments[#onError];
    final bool cancelOnError = invocation.namedArguments[#cancelOnError];

    return new Stream<List<int>>.fromIterable(<List<int>>[kTransparentImage])
        .listen(onData, onDone: onDone, onError: onError, cancelOnError: cancelOnError);
  });

  return client;
}

Jika dilaukan retest maka akan berjalan lancar sebagai mana mestinya. Selain test untuk test image bisa dilaukan juga pada api request dengan melakukan sedikit modifikasi seperti berikut:

import 'dart:io';

import 'package:transparent_image/transparent_image.dart';

HttpOverrides.global = TestHttpOverrides();

class TestHttpOverrides extends HttpOverrides {
  @override
  HttpClient createHttpClient(io.SecurityContext context) {
    return createMockHttpClient(context);
  }
}


HttpClient createMockHttpClient(SecurityContext _) {
  final MockHttpClient client = MockHttpClient();
  final MockHttpClientRequest request = MockHttpClientRequest();
  final MockHttpClientResponse response = MockHttpClientResponse();
  final MockHttpHeaders headers = MockHttpHeaders();
  final List<int> data = [];
  
  when(client.getUrl(any)).thenAnswer((Invocation invocation) {
    final Uri target = invocation.positionalArguments[0];
    // handle for path
    if (target.path.contains('/wp-json/wp/v2/posts')) {
      data.addAll(utf8.encode(kPostsResult));
    } else if (target.path.contains('/wp-json/wp/v2/posts/')) {
      data.addAll(utf8.encode(kPostResult));
    } else if (target.path.contains('/wp-json/wp/v2/categories')) {
      data.addAll(utf8.encode(kCategoriesResult));
    } else {
      data.addAll(kTransparentImage);
    }
    // when(request.uri).thenReturn(target);
    return Future<HttpClientRequest>.value(request);
  });
  when(request.headers).thenReturn(headers);
  when(request.close())
      .thenAnswer((_) => new Future<HttpClientResponse>.value(response));
  when(response.contentLength).thenReturn(data.length);
  when(response.statusCode).thenReturn(HttpStatus.ok);
  when(response.listen(any)).thenAnswer((Invocation invocation) {
    final void Function(List<int>) onData = invocation.positionalArguments[0];
    final void Function() onDone = invocation.namedArguments[#onDone];
    final void Function(Object, [StackTrace]) onError =
        invocation.namedArguments[#onError];
    final bool cancelOnError = invocation.namedArguments[#cancelOnError];

    // Returns a mock HTTP client that responds with an image to all requests.
    return Stream<List<int>>.fromIterable([data]).listen(onData,
        onDone: onDone, onError: onError, cancelOnError: cancelOnError);
  });

  return client;
}

Untuk Api test saya menggunakan json file sebagai responnya kemudian seteleh file yang diinginkan saya encode format utf8. Jika path pada url yang ingin diakses tidak sesuai dengan salah satu path maka akan mengirim berupa gambar.

Melakaukan Mock Untuk Platform Channel

Kita bisa melakukan simulasi platform channel. sehingga bisa membuat invokeMethod menggunakan setMockMethodCallHandler terhadap sebuah MethodCall. Untuk mengetahui nama dari MethodChannel bisa pada vsCode tekan Ctrl+click pada class yang menggunakan MethodChannel. Berikut contoh penggunaan method channel untuk mock path_provider module.

final List<MethodCall> log = <MethodCall>[];
String response;

const MethodChannel pathChannel = MethodChannel('plugins.flutter.io/path_provider');

pathChannel.setMockMethodCallHandler((MethodCall methodCall) async {
  log.add(methodCall);
  return response ?? '';
});

Untuk integrated test saya akan bahas pada postingan selanjutnya, karena integrated test ini agak beda dengan kedua jenis test di atas dan saya pikir harus membuat postingan yang khusus membahas topik ini. Kalian bisa melakukan request post yang sebaiknya saya buat di kolom komentar di bawah baik mengenai dart atau flutter, atau hal lainnya. Jika ada hal yang kurang tepat atau kesalahan feedback yang membangun saya terima.

  1. home
  2. Belajar Melakukan Testing Di Flutter