← スキル一覧に戻る

flutter-testing
by ht2-io
⭐ 0🍴 1📅 2026年1月19日
SKILL.md
name: flutter-testing description: Use when writing, running, or debugging Flutter tests. Provides testing patterns, commands, and best practices for the Rmotly app.
Flutter Testing Skill
Test Directory Structure
rmotly_app/test/
├── unit/ # Unit tests (fast, isolated)
│ ├── core/
│ │ └── utils/
│ │ └── template_parser_test.dart
│ └── features/
│ └── dashboard/
│ ├── dashboard_viewmodel_test.dart
│ └── control_repository_test.dart
├── widget/ # Widget tests (UI components)
│ └── features/
│ └── dashboard/
│ ├── button_control_widget_test.dart
│ └── dashboard_view_test.dart
├── integration/ # Integration tests (full flows)
│ └── dashboard_flow_test.dart
├── golden/ # Golden/snapshot tests
│ ├── control_cards_test.dart
│ └── goldens/ # Golden image files
└── fixtures/ # Shared test data
└── test_data.dart
Test Commands
# Run all tests
cd rmotly_app && flutter test
# Run with coverage report
flutter test --coverage
# Generate HTML coverage report
genhtml coverage/lcov.info -o coverage/html
open coverage/html/index.html
# Run specific test file
flutter test test/unit/features/dashboard/dashboard_viewmodel_test.dart
# Run tests matching a pattern
flutter test --name "DashboardViewModel"
# Run tests with tags
flutter test --tags unit
flutter test --exclude-tags integration
# Update golden files
flutter test --update-goldens
# Run with verbose output
flutter test --reporter expanded
Mocking with Mocktail
Mocktail is the preferred mocking library (not Mockito).
Creating Mocks
import 'package:mocktail/mocktail.dart';
// Create mock class
class MockControlRepository extends Mock implements ControlRepository {}
class MockApiService extends Mock implements ApiService {}
void main() {
late MockControlRepository mockRepo;
// Register fallback values for custom types (required for any())
setUpAll(() {
registerFallbackValue(Control(
id: 0,
name: '',
controlType: ControlType.button,
position: 0,
));
});
setUp(() {
mockRepo = MockControlRepository();
});
}
Stubbing Methods
// Return a value
when(() => mockRepo.getControls())
.thenReturn([testControl]);
// Return async value
when(() => mockRepo.getControls())
.thenAnswer((_) async => [testControl]);
// Throw an exception
when(() => mockRepo.getControls())
.thenThrow(NetworkException('Connection failed'));
// Use any() for flexible matching
when(() => mockRepo.saveControl(any()))
.thenAnswer((_) async => testControl);
// Capture arguments
when(() => mockRepo.saveControl(captureAny()))
.thenAnswer((_) async => testControl);
final captured = verify(() => mockRepo.saveControl(captureAny())).captured;
Verifying Calls
// Verify called
verify(() => mockRepo.getControls()).called(1);
// Verify never called
verifyNever(() => mockRepo.deleteControl(any()));
// Verify call order
verifyInOrder([
() => mockRepo.getControls(),
() => mockRepo.saveControl(any()),
]);
Test Patterns
AAA Pattern (Arrange-Act-Assert)
test('calculates total correctly', () {
// Arrange - Set up test data and dependencies
final calculator = PriceCalculator();
final items = [Item(price: 10), Item(price: 20)];
// Act - Execute the code under test
final total = calculator.calculateTotal(items);
// Assert - Verify the results
expect(total, 30);
});
Testing Async Code
test('loads data asynchronously', () async {
// Arrange
when(() => mockRepo.fetchData())
.thenAnswer((_) async => testData);
// Act
final result = await service.loadData();
// Assert
expect(result, testData);
});
Testing Streams
test('emits values in order', () {
final stream = controller.stream;
expectLater(
stream,
emitsInOrder([1, 2, 3, emitsDone]),
);
controller.add(1);
controller.add(2);
controller.add(3);
controller.close();
});
Testing Riverpod Providers
test('loads controls on initialization', () async {
// Create container with overrides
final container = ProviderContainer(
overrides: [
controlRepositoryProvider.overrideWithValue(mockRepository),
],
);
addTearDown(container.dispose);
// Stub the mock
when(() => mockRepository.getControls())
.thenAnswer((_) async => [testControl]);
// Read the provider
final viewModel = container.read(dashboardViewModelProvider.notifier);
await viewModel.loadDashboard();
// Verify state
final state = container.read(dashboardViewModelProvider);
expect(state.valueOrNull?.controls.length, 1);
});
Widget Testing
testWidgets('button displays label', (tester) async {
// Arrange & Act
await tester.pumpWidget(
MaterialApp(
home: ButtonControlWidget(
control: testControl,
onPressed: () {},
),
),
);
// Assert
expect(find.text('Test Button'), findsOneWidget);
});
testWidgets('button triggers callback on tap', (tester) async {
var tapped = false;
await tester.pumpWidget(
MaterialApp(
home: ButtonControlWidget(
control: testControl,
onPressed: () => tapped = true,
),
),
);
// Act
await tester.tap(find.byType(ElevatedButton));
await tester.pump();
// Assert
expect(tapped, isTrue);
});
Golden Testing
testWidgets('control card matches golden', (tester) async {
await tester.pumpWidget(
MaterialApp(
home: ControlCard(control: testControl),
),
);
await expectLater(
find.byType(ControlCard),
matchesGoldenFile('goldens/control_card.png'),
);
});
Test Data Fixtures
Create reusable test data in test/fixtures/test_data.dart:
final testControl = Control(
id: 1,
name: 'Test Button',
controlType: ControlType.button,
position: 0,
config: '{}',
);
final testAction = Action(
id: 1,
name: 'Test Action',
httpMethod: 'POST',
urlTemplate: 'https://api.example.com/test',
);
final testUser = User(
id: 1,
email: 'test@example.com',
displayName: 'Test User',
);
Coverage Targets
| Component | Target |
|---|---|
| ViewModels | 90%+ |
| Repositories | 80%+ |
| Services | 80%+ |
| Utilities | 90%+ |
| Widgets (critical) | 70%+ |
What NOT to Test
- Trivial getters/setters
- Flutter framework internals
- Third-party package behavior
- Generated code
スコア
総合スコア
50/100
リポジトリの品質指標に基づく評価
✓SKILL.md
SKILL.mdファイルが含まれている
+20
○LICENSE
ライセンスが設定されている
0/10
○説明文
100文字以上の説明がある
0/10
○人気
GitHub Stars 100以上
0/15
○最近の活動
3ヶ月以内に更新がある
0/10
○フォーク
10回以上フォークされている
0/5
✓Issue管理
オープンIssueが50未満
+5
✓言語
プログラミング言語が設定されている
+5
○タグ
1つ以上のタグが設定されている
0/5
レビュー
💬
レビュー機能は近日公開予定です