Bloc Unit Testing pada Flutter
Setelah melakukan refactoring kode dan menggunakan Bloc Pattern, membuat Unit Test pada kode menjadi lebih mudah. Berikut ini adalah salah satu penerapan unit testing yang dilakukan oleh LearnFazz untuk Bloc fitur See All Comments (Comment List).
Pertama-tama, perlu dilakukan import package flutter_test, mockito, Comment List Bloc, dan package-package lain yang bersangkutan.
import 'package:bloc/bloc.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:learn_fazz/blocs/comment_list/comment_list.dart';
import 'package:learn_fazz/models/comment_list_result.dart';
import 'package:learn_fazz/repositories/comment_repository.dart';
import 'package:mockito/mockito.dart';
import '../../test_helpers/mock_classes.dart';
import '../../test_helpers/simple_bloc_delegate.dart';
import '../../test_helpers/test_factories/comment_factory.dart';
Pada setUp, kami membuat mock instansi dari CommentRepository menggunakan mockito. Kemudian kami juga menginisiasi CommentListBloc dengan mockCommentRepository sebagai dependensinya.
void main() {
CommentListBloc bloc;
CommentRepository mockCommentRepository;
final CommentListState initialState = GetCommentListSuccess(CommentListResult.empty());
setUp(() {
mockCommentRepository = MockCommentRepository();
bloc = CommentListBloc(commentRepository: mockCommentRepository);
});
Test pertama yang kami lakukan adalah sanity test untuk memastikan bahwa ketika dispose dilakukan maka state dari Bloc tidak diupdate.
test('dispose should not emit new states', () {
expectLater(bloc.state, emitsInOrder([]));
bloc.dispose();
});
Untuk melakukan test pada CommentList success, perlu dibuat skenario yaitu untuk pemanggilan fungsi getCommentList akan dihasilkan objects list dari CommentList. Kemudian kami membuat sebuah setup mengenai ekspektasi event apa saja yang dihasilkan oleh Bloc untuk mengetahui apakah comment list sukses dijalankan. Ekspektasi tersebut dideklarasikan pada expectLater dimana salah satu parameternya emitsInOrder berisi pergantian state dari initialState, GetCommentListLoading(), hingga CommentListSuccess(). Pada line terakhir dilakukan bloc dispatch untuk test fungsi yang sudah dibuat.
test('CommentList success', () {
when(mockCommentRepository.getCommentList(any, any)).thenAnswer(
(_) => Future<CommentListResult>.value(
CommentFactory.constructDefaultCommentListResult(),
),
);
final _initialState = initialState;
expectLater(
bloc.state,
emitsInOrder([
_initialState,
GetCommentListLoading(),
GetCommentListSuccess(CommentFactory.constructDefaultCommentListResult())
])).then((_) => bloc.dispose()); bloc.dispatch(GetCommentList(courseId: 1, offset: 0, postId: 1));
});
Test untuk CommentList fail memiliki kode yang hampir sama dengan CommentList success. Perbedaannya terletak pada skenario awal yang melakukan throw 404 error untuk membuat getCommentList fail. Perbedaan lainnya adalah state emitsInOrder terakhir merupakan GetCommentListFailure().
test('CommentList should fail', () {
when(mockCommentRepository.getCommentList(any, any)).thenThrow('404 Error');
expectLater(
bloc.state,
emitsInOrder([
initialState,
GetCommentListLoading(),
GetCommentListFailure('404 Error')
])).then((_) => bloc.dispose());
bloc.dispatch(GetCommentList(courseId: 1, offset: 0, postId: 1));
});
}
Mock Object
Apa itu mock dan mengapa kita harus menggunakan mock object saat melakukan testing?
Sebuah unit test hanya melakukan test terpisah tergantung dengan fungsionalitasnya. Hal ini berarti kelas atau objek yang digunakan saat melakukan unit test tidak boleh menimbulkan efek lain untuk kelas lainnya yang tidak bersangkutan dengan test tersebut. Contohnya, dalam beberapa kasus, test diperuntukkan bagi kelas yang mengambil data langsung dari live web service atau database. Hal ini termasuk sulit, karena pemanggilan live service atau database dapet memerlambat eksekusi test, dan jika terdapat perubahan pada database atau live web service, akan susah dipastikan apakah test tersebut akan sukses atau gagal. Oleh karena itu, unit test ini dapat dibantu dengan menggunakan test replacement/doubles yang merupakan test pengganti untuk dependensi nyata. Salah satu dari test replacement ini adalah menggunakan mock object.
Mock object adalah implementasi dummy untuk sebuah interface atau class dimana kita menentukan sendiri output dari pemanggilan method tertentu. Objek ini dikonfigurasikan untuk menjalankan suatu behavior pada saat melakukan testing. Mock biasanya dapat merekam interaksi dengan sistem dan test dapat memvalidasi interaksi tersebut. Mock object dapat dibuat secara manual atau menggunakan framework. Pada implementasi yang saya lakukan, saya menggunakan Mockito package : https://pub.dev/packages/mockito.
Referensi :