flutter + reduxでのMiddleware(非同期処理)のテスト
概要
flutter + reduxの構成でのmiddleareの単体テストの書き方を紹介します。テスト用のDSLはどの言語で勉強するのも大変ですね。
※reducerのテストはdispatchしてstateをexpectするだけなのでここでは説明しません
Prerequisite
pubspec.ymlは以下の通り。APIコール部分はモックするためにMockitoを使用します。
dependencies:
flutter_redux: ^0.6.0
dev_dependencies:
flutter_test:
sdk: flutter
mockito: ^4.1.1
テスト対象のmiddleware
あるActionが呼ばれたらAPIをコール、その前後でLoadingに関するStartとCompleteのActionがdispatchされるmiddlewareのテストを書くこととします。
以下のようなmiddlewareを想定してください。
Middleware<AppState> rootMiddleware(MyClient myClient) =>
return TypedMiddleware<AppState, FetchStartAction>(_fetch(myClient)
}
void Function(
Store<AppState> store,
FetchStartAction action,
NextDispatcher next,
) _fetch(MyClient myClient) {
return (store, action, next) {
next(LoadingAction());
myClient.fetch().then((res) {
next(FetchSucceedAction(res: res));
})
.whenComplete(() {
next(LoadCompleteAction());
});
};
}
テストコードのセットアップ
具体的なテストコードを書く前の準備として必要なクラスのモック化、およびStoreの宣言などを行います。
// APIコールを行うclientをモック化
class MockClient extends Mock implements MyClient {}
class Watcher extends Mock implements MiddlewareClass<AppState> {}
void main() {
final mockClient = MockClient();
final watcher = Watcher();
// storeの初期化
final store = Store<AppState>(
appReducer,
initialState: AppState.init(),
middleware: [rootMiddleware(
myClient: mockClient,
)]..add(watcher),
);
// APIクライアントのfetch()をモックする
when(mockClient.fetch())
.thenAnswer((_) {
return Future.value(mockedResult);
});
// 各テストの前にFetchStartActionを呼ぶ
setUp(() {
store.dispatch(FetchStartAction());
});
// 各テスト後にmockMiddlewareを初期化する
tearDown(() {
reset(watcher);
});
// 全テスト終了後にmockClientを初期化する
tearDownAll(() {
reset(mockClient);
});
middlewareに対して、既にrootMiddleware
が代入されているのに..add(watcher)
していますが、
これは実際のmiddlewareの挙動を確認するためのインスタンスです。
Mock
をextends
したクラスのインスタンスは呼ばれたメソッドを全て記録しています。verify()
を使ってどのメソッドが呼ばれたかを後から検証できます。
MiddlewareからActionがdispatchされたことを確認する
先にコードを見せますが、以下のようになります。
group('before call api', () {
test('dispatch LoadingAction', () async {
verify<void>(
watcher.call(
store,
predicate<LoadingAction>((action) => action is LoadingAction),
any,
),
);
});
});
この記事で1番大事なのはこの部分↓ですが、分かりにくいので分解して解説します。
verify<void>(
watcher.call(
store,
predicate<LoadingAction>((action) => action is LoadingAction),
any,
),
);
まず、上のコードを単純化すると以下のようになります。
verify(watcher.call(...));
verify(mockedObj.hoge())
はmockedObj.hoge()
が呼ばれたか検証します。つまり、ここではwatcher.call()
が呼ばれたか検証しています。
watcher.call(
store,
predicate<LoadingAction>((action) => action is LoadingAction),
any,
)
次に、watcher
はdispatchされたActionを全て記録しているため、このactionの型をpredicateで制限して検証しています。
これらを組み合わせると、「LoadingAction
が正しくdispatchされたか」を検証できます。
verify<void>(
watcher.call(
store,
predicate<LoadingAction>((action) => action is LoadingAction),
any,
),
);
※ただし、watcherが記録しているのはそのテスト中に実行されたAction全てであるため「verify()
したActionがテスト対象のmiddleewareからdispatchされたものであるかは検証できない」という点に注意してください。
MiddlewareからdispatchされたActionのプロパティを確認する
基本的に要領は同じです。先にコードを見せます。
test("action's result is mockedResult", () async {
await untilCalled<void>(
watcher.call(
store,
predicate<LoadCompleteAction>(
(action) => action is LoadCompleteAction),
any,
),
);
verify<void>(
watcher.call(
store,
predicate<FetchSucceedAction>(
(action) {
return action is FetchSucceedAction &&
action.result, mockedResult;
},
),
any,
),
);
});
先ほどと違うのは以下の部分です。
await untilCalled<void>(
watcher.call(
store,
predicate<LoadCompleteAction>(
(action) => action is LoadCompleteAction),
any,
),
);
これはuntilCalled
でLoadCompleteAction
が記録されるのを待っています。LoadCompleteAction
は.whenComplete
内でdispatchされるように書いているので、これによってAPIコールの結果を待っています。
参考:https://pub.dev/documentation/mockito/latest/mockito/untilCalled.html
verify<void>(
watcher.call(
store,
predicate<FetchSucceedAction>(
(action) {
return action is FetchSucceedAction &&
action.result, mockedResult;
}
),
any,
),
);
後は↑ですが、actionに渡した値ががAPIclientのモックの値と同じかを検証しています。
まとめ
middlewareのテスト、やはり
verify()
したActionがテスト対象のmiddleewareからdispatchされたものであるかは検証できない
のがちょっと厳しい感はありますね…
dartのreduxでmiddlewareをテストするコードがググってもあんまりサンプルが出てこないので、もっと良さそうな書き方をご存じの方はご教示くださいm(__)m
雑記
Actionがdispatchされたかどうかよりもstateがどう変わったかのレベルでテストしたほうが良いかもしれないと書いてからおもいました
でもそれだと1回のActionで複数回変更されるLoadingのようなstateの変更をテストするのが難しいからmiddlewareをモックしてverify()していくしかないとやっぱりおもいました
メモ:API1行解説
redux
middleware.call()
dispatchされたactionをreducerが処理する前にmiddlewareが呼ばれた時の関数。
storeを初期化する際に代入した全てのmiddlewareに対してcall()
が呼ばれる。
例えば、公式のexampleにもある通り、以下のようにすればprint()
がreducerの前に実行される。
class LoggingMiddleware extends MiddlewareClass<int> {
call(Store<int> store, action, NextDispatcher next) {
print('${new DateTime.now()}: $action');
next(action);
}
}
// Create your store with the loggingMiddleware
final store = new Store<int>(
counterReducer,
middleware: [new LoggingMiddleware()],
);
TypedMiddleware
はこれを便利にしただけのクラスなのでこちらも当然call()
が呼ばれる。ただしこちらは<T>
に代入したActionの場合のみ。
dart/test
predicate()
テストしたい値を元に何かしらの計算をしてアサーションする時に使う関数(要はMatcherを返しているだけ)。
具体的には以下のようにして使う。
class Rect {
Rect(this.length, this.width);
final int length;
final int width;
int get area => length * width;
}
test('what is predicate', () {
final hasCorrectArea = predicate<Rect>((rect) {
return rect.area == rect.length * rect.width;
});
final myRect = Rect(10, 10);
expect(myRect, hasCorrectArea);
});
setup(), tearDown()
各テストの前後に処理を挟む関数。setup()で各テストの前にdispatch()、後にmockMiddlewareの初期化を行ったりする。
mockito
verify()
以下のようにして、モックされたオブジェクトがそのメソッドを呼んだかを判定するメソッド。
cat.eatFood("chicken"); // catはmockをextentedしたクラスのオブジェクト
verify(cat.eatFood("chicken")); // passed
verify(cat.eatFood("fish")); // failed
参考
Author And Source
この問題について(flutter + reduxでのMiddleware(非同期処理)のテスト), 我々は、より多くの情報をここで見つけました https://qiita.com/canisterism/items/d625014be4a70b3236b7著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .