iT邦幫忙

2021 iThome 鐵人賽

DAY 10
0
Mobile Development

Flutter - 複製貼上到開發套件之旅系列 第 10

【第十天 - Flutter Bloc Unit Test+Mocktail 範例】

前言

今日的程式碼 => GITHUB
接續上一篇 【第九天 - Flutter Bloc+Cubit 架構教學】
今天要來介紹單元測試,如何和 bloc pattern 結合,如何使用 mocktail 產生假資料。

設定 YAML 檔案

dependencies:
  mocktail: ^0.1.4
dev_dependencies:
  bloc_test: ^8.1.0

post_bloc_test

MockRepoImp,這邊是我們示範如何寫假的資料,但是我自己不喜歡這樣寫,因為這樣寫會需要寫很多很多的 Code,因此後面我們有介紹另外一種寫法,使用了強大的 mocktail套件,可以幫助我們解決這個問題。
Test

void main() {
 group('這個群組的測試名稱', () {
   test('測試名稱', () async{
     
   });
 });
}
// 架構 樣子
Main
----|Group
----|-----|Test
----|-----|Test
----|-----|Test

Bloc_Test 官方文件

  • 一樣釋放 blocTest<Bloc,Test>
  • build 則是我們要去實作 new Bloc
  • act 事件
  • seed 設定當下狀態,比方說我們觸發這個狀態的時候,當下狀態是哪一個。
  • wait 模擬 delay 時間
  • skip 是一個可選的int,可用於跳過任意數量的狀態。skip默認為 0。
  • verify 是用來檢查程式碼是否有觸發 function 並且呼叫了幾次
  • errors 用來檢查 exception
  • expect 預計執行的 state
  • tearDown是可選的,可用於在測試運行後執行任何代碼。tearDown應該用於在特定測試用例之後進行清理。

verify = 裡面會有現在 state 的狀態,然後我們就可以拿到 state 裡面的參數來做驗證了。
expect = 前面釋放程式碼結果,後面釋放預計結果。
isA<>() = <> 裡面就釋放類別。

class MockRepoImp extends IPostRepository {
  @override
  Future<List<PostModel>> fetchData() async => [
        PostModel(
            userId: 1, id: 1, title: 'Mickey title', body: 'this is the body'),
        PostModel(
            userId: 2, id: 2, title: 'Ruby title', body: 'this is the body'),
      ];
}

void main() {
  group('Post Bloc Test', () {
    blocTest<PostBloc, IPostState>(
      '確認 FetchPostData 的狀態是對的',
      build: () => PostBloc(repository: MockRepoImp()),
      act: (bloc) => bloc.add(FetchPostData()),
      // 設定事件的初始狀態
      seed: () => PostLoading(),
      // 設定 Delay 時間
      wait: const Duration(milliseconds: 300),
      expect: () => [
        // isA<PostLoading>(), // 初始化的狀態並不會被觸發
        isA<PostSuccess>(),
      ],
    );

    blocTest<PostBloc, IPostState>(
      '預設 Sort by Id',
      build: () => PostBloc(repository: MockRepoImp()),
      act: (bloc) => bloc.add(FetchPostData()),
      verify: (bloc) {
        final _state = bloc.state as PostSuccess;
        expect(_state.postList.length, 2);
        expect(_state.postList[0].id, 1);
        expect(_state.postList[1].id, 2);
      },
    );
    //
    blocTest<PostBloc, IPostState>(
      'Sorted by title',
      build: () => PostBloc(repository: MockRepoImp()),
      act: (bloc) => bloc
        ..add(FetchPostData())
        ..add(SortPostEvent(sortState: SortState.title)),
      expect: () => [
        isA<PostSuccess>(),
        isA<PostSuccess>(),
      ],
      verify: (bloc) {
        final _state = bloc.state as PostSuccess;
        expect(_state.postList.length, 2);
        expect(_state.postList[0].id, 1);
        expect(_state.postList[1].id, 2);
      },
    );
  });
}

Mock 是什麼?

Mock 就是模擬行為,以下面的範例為例子,我們的 PostService 有一個 function 叫做 fetchData(),可是今天我單元測試,我想要能夠控制 fetchData 回傳的資料是什麼東西。因為這樣才可以達到後面我去驗證後面的結果。所以使用 Mock 可以讓 fetchData 回傳假的資料,回傳任何型態的資料。有點類似覆寫 fetchData 這個方法的感覺。

post_api_test

我們現在想要 Mock PostService 的話,我們就 extends Mock 然後 implements PostService 就可以了。

class MockPostService extends Mock implements PostService {}

這行是我們要覆寫 fetchData() 然後希望他的假資料是 _mockList

when(() => _post_service.fetchData()).thenAnswer((_) async => _mockList);
class MockPostService extends Mock implements PostService {}

final _mockList = [
  PostModel(userId: 1, id: 1, title: 'Mickey title', body: 'this is the body'),
  PostModel(userId: 2, id: 2, title: 'Ruby title', body: 'this is the body'),
];

void main() {
  group('test_API', () {
    final _post_service = MockPostService();
    test('returns List<PostModel> and called only one time if the http call completes successfully', () async {
      //Arrange
      when(() => _post_service.fetchData()).thenAnswer((_) async => _mockList);
      // act
      final act = await _post_service.fetchData();
      // assert
      expect(act, isA<List<PostModel>>());
      verify(() => _post_service.fetchData()).called(1);
    });
  });
}

上一篇
【第九天 - Flutter Bloc+Cubit 架構教學】
下一篇
【第十一天 - Flutter GetX 架構教學】
系列文
Flutter - 複製貼上到開發套件之旅30

尚未有邦友留言

立即登入留言