Null安全のその先へ:Mocktailで挑む「型定義の真実」と堅牢なテスト戦略
DartのSound Null Safetyは、単なる「Nullポインタ例外を防ぐためのガードレール」ではない。それは、コンパイル時に型システムがコードの「意図」を保証するための、強力な静的解析エンジンだ。
しかし、テストコードを書く段になると、多くのエンジニアが `late` や `!`(強制アンラップ)の誘惑に負け、型システムの厳密性を自ら破壊している。特に `mocktail` や `mockito` を用いたテストダブルの設計において、「テストのためだけに型定義を汚染する」のは、アーキテクチャの敗北だ。
今回は、Dartの型システムを正しく掌握し、堅牢で保守性の高いモック設計を行うための極意を伝授する。
—
1. なぜ「雑なモック」がプロダクションの信頼を損なうのか
多くの現場で散見されるアンチパターンがこれだ。
// アンチパターン:とりあえず null を返して例外を回避する
when(mockRepo.getUser()).thenAnswer((_) => Future.value(null));
もし `getUser()` が `Future
Dart VMは、型定義が正しければ、実行時のメモリレイアウトを最適化する。 テストのために型を緩めることは、単なるコードの不備ではなく、コンパイラに対する背信行為であり、将来のバグの温床となる。
—
2. 推奨設計:`Null` を許容しないテストダブルの構築
テストにおいて「値が存在しない状態」をモック化したい場合、型を緩めるのではなく、「その型が持つべき状態」を正しく表現するのがDart流だ。
実践的なモック設計例
例えば、`UserRepository` が `Future
import ‘package:mocktail/mocktail.dart’;
// 1. 成功ケース:完全なオブジェクトを返す
// 2. 失敗ケース:例外を投げる (throw)
// 3. 不在ケース:例外を投げる、あるいは特定のエラー型を返す
class MockUserRepository extends Mock implements UserRepository {}
void main() {
final mockRepo = MockUserRepository();
// 良い設計:存在しないIDへのアクセスは、nullを返すのではなく「例外」を定義する
// これにより、呼び出し側の catch ブロックが正しく型安全に機能する
when(() => mockRepo.getUser(‘unknown’))
.thenThrow(UserNotFoundException());
// …
}
「値が返らないこと」をNullで表現するのは、型システムを無駄に複雑にする。「値が存在しないときは例外を投げる」というドメインルールをモックに徹底させることで、呼び出し側のコードには常に「有効なインスタンスが返ってくる」という強い契約(Contract)を維持できる。
—
3. コンポーネント設計における `late` との付き合い方
テスト対象のコンポーネントが `late` プロパティを多用している場合、`setUp` での初期化漏れがランタイムエラーを引き起こす。これを防ぐには、コンストラクタインジェクションを徹底し、テスト内では完全なインスタンスを生成するのが正攻法だ。
class UserProfileBloc {
final UserRepository repository;
// late を排除し、コンストラクタで保証する
UserProfileBloc(this.repository);
}
もし、どうしても `late` を排除できない古いコードベースを扱うなら、テストダブル生成時に 「値が未定義のまま放置されることを許さない」 構成を強いること。
—
4. パフォーマンスとVMの最適化:`any()` の濫用を避ける
`mocktail` の `any()` は便利だが、多用するとテストの網羅性と可読性を低下させる。Dart VMは、型引数が明確であれば、メソッド呼び出しのディスパッチを最適化できる。
// 避けるべき記述
when(() => mockRepo.update(any())).thenAnswer((_) async => true);
// 推奨:具体的な型と値の期待値を明示する
// これにより、テスト実行時の型チェックが厳密に行われ、
// 将来的なリファクタリング時に型不整合を即座に検知できる
when(() => mockRepo.update(any(that: isA
.thenAnswer((_) async => true);
`isA
—
結論:型システムは「敵」ではなく「最も優秀な同僚」
Null安全環境におけるモックテストの極意は、「型を緩めることで楽をしようとしない」 ことに尽きる。
- Nullを返させない: 代わりに例外を投げるか、デフォルト値を定義する。
- anyを乱用しない: `isA
()` で型を固定し、契約を維持する。 - lateを撲滅する: インジェクションを徹底し、初期化の不確実性を排除する。
Dartの型システムは、あなたのコードが正しく動くための最短ルートを示している。テストコードにおいてそのガイドを無視することは、飛行機の計器を無視して雲の中を飛ぶようなものだ。
次に `mocktail` を叩くとき、一度自問してほしい。「このモックは、プロダクションコードの型定義を正しく尊重しているか?」 と。その問いこそが、堅牢なアプリケーションを構築するエンジニアの矜持である。