ํญ๋ชฉ ๋ด์ฉ
Invoke /test
Aliases /test:unit, /test:widget, /test:bloc, /test:patrol
Category petmedi-development
Complexity moderate
MCP Servers serena, context7
/test #
Context Framework Note : Activates when writing test code.
Triggers #
When writing new test files
When improving test coverage
During TDD/BDD development
When writing Patrol E2E tests
Context Trigger Pattern #
/ test { type} { target} [ -- options]
Parameters #
Parameter Required Description Example
type
โ
Test Type
unit, widget, bloc, bdd, patrol
target
โ
Test target
GetUserUseCase, HomeBloc, LoginPage
--feature
โ
Feature module
auth, home
--coverage
โ
Coverage target
80, 90 (default: 80)
Test Structure #
feature/ { location} / { feature_name} / test/
โโโ src/
โ โโโ domain/
โ โ โโโ usecase/ # UseCase ๋จ์ ํ
์คํธ
โ โโโ data/
โ โ โโโ repository/ # Repository ํ
์คํธ ( mocked)
โ โโโ presentation /
โ โโโ bloc/ # BLoC ํ
์คํธ
โ โโโ widget/ # Widget ํ
์คํธ
โโโ feature/ # BDD ํ
์คํธ
โโโ { feature} . feature
โโโ step/
UseCase Unit Test #
import 'package:flutter_test/flutter_test.dart' ;
import 'package:mocktail/mocktail.dart' ;
import 'package:dependencies/dependencies.dart' ;
class MockI { Feature } Repository extends Mock implements I { Feature } Repository { }
void main ( ) {
late Get { Entity } UseCase useCase;
late MockI { Feature } Repository mockRepository;
setUp ( ( ) {
mockRepository = MockI { Feature } Repository ( ) ;
useCase = Get { Entity } UseCase ( mockRepository) ;
} ) ;
group ( 'Get{Entity}UseCase' , ( ) {
final tEntity = { Entity } ( id: 1 , name: 'Test' ) ;
final tParams = Get { Entity } Params ( id: 1 ) ;
test ( 'should return entity when repository succeeds' , ( ) async {
// Arrange
when ( ( ) => mockRepository. get { Entity } ( any ( ) ) )
. thenAnswer ( ( _) async => Right ( tEntity) ) ;
// Act
final result = await useCase ( tParams) ;
// Assert
expect ( result, Right ( tEntity) ) ;
verify ( ( ) => mockRepository. get { Entity } ( 1 ) ) . called ( 1 ) ;
verifyNoMoreInteractions ( mockRepository) ;
} ) ;
test ( 'should return failure when repository fails' , ( ) async {
// Arrange
final tFailure = ServerFailure ( 'Error' ) ;
when ( ( ) => mockRepository. get { Entity } ( any ( ) ) )
. thenAnswer ( ( _) async => Left ( tFailure) ) ;
// Act
final result = await useCase ( tParams) ;
// Assert
expect ( result, Left ( tFailure) ) ;
} ) ;
} ) ;
}
BLoC Test #
import 'package:bloc_test/bloc_test.dart' ;
import 'package:flutter_test/flutter_test.dart' ;
import 'package:mocktail/mocktail.dart' ;
class MockGet { Entity } UseCase extends Mock implements Get { Entity } UseCase { }
void main ( ) {
late { Feature } Bloc bloc;
late MockGet { Entity } UseCase mockUseCase;
setUp ( ( ) {
mockUseCase = MockGet { Entity } UseCase ( ) ;
bloc = { Feature } Bloc ( ) ;
} ) ;
tearDown ( ( ) {
bloc. close ( ) ;
} ) ;
group ( '{Feature}Bloc' , ( ) {
test ( 'initial state is Initial' , ( ) {
expect ( bloc. state, const { Feature } State . initial ( ) ) ;
} ) ;
blocTest< { Feature } Bloc , { Feature } State > (
'emits [Loading, Loaded] when load succeeds' ,
build: ( ) => bloc,
act: ( bloc) => bloc. load ( ) ,
expect: ( ) => [
const { Feature } State . loading ( ) ,
isA< { Feature } State > ( ) . having (
( s) => s. maybeMap ( loaded: ( l) => l. items, orElse: ( ) => null ) ,
'items' ,
isNotEmpty,
) ,
] ,
) ;
blocTest< { Feature } Bloc , { Feature } State > (
'emits [Loading, Error] when load fails' ,
build: ( ) => bloc,
act: ( bloc) => bloc. load ( ) ,
expect: ( ) => [
const { Feature } State . loading ( ) ,
isA< { Feature } State > ( ) . having (
( s) => s. maybeMap ( error: ( e) => e. failure, orElse: ( ) => null ) ,
'failure' ,
isNotNull,
) ,
] ,
) ;
} ) ;
}
import 'package:flutter/material.dart' ;
import 'package:flutter_test/flutter_test.dart' ;
import 'package:flutter_bloc/flutter_bloc.dart' ;
import 'package:mocktail/mocktail.dart' ;
class Mock { Feature } Bloc extends MockBloc < { Feature } Event , { Feature } State >
implements { Feature } Bloc { }
void main ( ) {
late Mock { Feature } Bloc mockBloc;
setUp ( ( ) {
mockBloc = Mock { Feature } Bloc ( ) ;
} ) ;
Widget buildTestWidget ( ) {
return MaterialApp (
home: BlocProvider < { Feature } Bloc > . value (
value: mockBloc,
child: const { Feature } Page ( ) ,
) ,
) ;
}
group ( '{Feature}Page' , ( ) {
testWidgets ( 'renders loading indicator when loading' , ( tester) async {
// Arrange
when ( ( ) => mockBloc. state) . thenReturn ( const { Feature } State . loading ( ) ) ;
// Act
await tester. pumpWidget ( buildTestWidget ( ) ) ;
// Assert
expect ( find. byType ( CircularProgressIndicator ) , findsOneWidget) ;
} ) ;
testWidgets ( 'renders list when loaded' , ( tester) async {
// Arrange
final items = [
{ Entity } ( id: 1 , name: 'Item 1' ) ,
{ Entity } ( id: 2 , name: 'Item 2' ) ,
] ;
when ( ( ) => mockBloc. state) . thenReturn (
{ Feature } State . loaded ( items: items) ,
) ;
// Act
await tester. pumpWidget ( buildTestWidget ( ) ) ;
// Assert
expect ( find. text ( 'Item 1' ) , findsOneWidget) ;
expect ( find. text ( 'Item 2' ) , findsOneWidget) ;
} ) ;
testWidgets ( 'shows error message when error' , ( tester) async {
// Arrange
when ( ( ) => mockBloc. state) . thenReturn (
{ Feature } State . error ( ServerFailure ( 'Network error' ) ) ,
) ;
// Act
await tester. pumpWidget ( buildTestWidget ( ) ) ;
// Assert
expect ( find. text ( 'Network error' ) , findsOneWidget) ;
} ) ;
} ) ;
}
BDD Test (TestDriver Pattern โ Recommended) #
.feature File #
@smoke
@auth
Feature : Login Page # ๋ก๊ทธ์ธ ํ์ด์ง
Background :
Given I am on the login page # ๋ก๊ทธ์ธ ํ์ด์ง
@validation
Scenario : Email field validation # ์ด๋ฉ์ผ ์ ํจ์ฑ ๊ฒ์ฌ
When I enter { ' invalid-email ' } in the email field # ์๋ชป๋ ์ด๋ฉ์ผ
Then the email error should be displayed # ์๋ฌ ํ์
@patrol - only
Scenario : Native back button # ๋ค์ดํฐ๋ธ ๋ฐฑ ๋ฒํผ
When I press the back button # ๋ฐฑ ๋ฒํผ
Then the app should navigate back # ์ด์ ํ๋ฉด ๋ณต๊ท
Step Function (TestDriver-based) #
import 'package:test_driver/test_driver.dart' ;
/// Usage: I enter {string} in the email field # ์ด๋ฉ์ผ ์
๋ ฅ
Future < void > iEnterInTheEmailField ( TestDriver driver, String param1) async {
await driver. enterText ( K . emailField, param1) ;
}
build.yaml (Dual test generation) #
targets:
$default:
builders:
bdd_test_gen| dual_test_gen:
enabled: true
generate_for:
- test/ src/ bdd/*.feature
options:
stepFolder: step
.feature โ .widget_test.dart + .patrol_test.dart generated simultaneously
Feature : { Feature } List View # { Feature } ๋ชฉ๋ก ์กฐํ
User can view { Feature } list.
Background :
Given the app is running # ์ฑ์ด ์คํ๋์ด ์๋ค
Scenario : List loads successfully # ๋ชฉ๋ก ์กฐํ ์ฑ๊ณต
When user navigates to { feature} screen
Then { entity} list is displayed
Patrol E2E Test #
import 'package:patrol/patrol.dart' ;
import 'package:test_driver/test_driver.dart' ;
void main ( ) {
patrolTest (
'Given logged in, When store, Then book list' ,
config: const PatrolTesterConfig ( settleTimeout: Duration ( seconds: 15 ) ) ,
( $) async {
final driver = PatrolTestDriver ( $) ;
await driver. tap ( K . navStore) ;
await driver. expectVisible ( K . bookList) ;
} ,
) ;
}
Test Commands #
# ์ ์ฒด ํ
์คํธ
melos run test
# Feature ๋ณ ํ
์คํธ
melos run test -- scope= feature_{ feature_name}
# ์ปค๋ฒ๋ฆฌ์ง ํฌํจ
melos run test : with - html- coverage
# BDD tests
melos run test: bdd
melos run test: bdd: select
# Code generation ( BDD โ _test. dart)
melos run build
# Patrol E2E
patrol test -- target integration_test/ scenarios/ smoke_test. dart
# Tag filtering
flutter test -- tags smoke
flutter test -- exclude- tags patrol- only
Core Rules #
Test Structure #
Arrange -> Act -> Assert pattern
Each test verifies only one behavior
Maintain independence between tests
Mocking #
Use mocktail package
Replace Repository/UseCase with Mock
Configure registerFallbackValue when needed
BDD Tests #
Follow Gherkin syntax
Step functions should use TestDriver (recommended) or WidgetTester
K class for centralized widget Key management
@widget-only, @patrol-only tags for test target control
Coverage #
Target minimum 80% code coverage
UseCase 100% testing required
BLoC main flow testing required
MCP Integration #
Phase MCP Server Purpose
Pattern analysis Context7 flutter_test, bloc_test, patrol docs
Code search Serena Existing test pattern reference
E2E execution patrol_mcp AI agent-driven E2E verification
Examples #
Generate UseCase Test #
/ test unit GetUserUseCase -- feature auth
Generate BLoC Test #
/ test bloc HomeBloc -- feature home
/ test widget LoginPage -- feature auth
Generate BDD Scenario (Dual Test) #
Patrol E2E #
/ test patrol auth_flow -- feature auth
References #
Detailed implementation: agents/test.md
BDD generation: commands/bdd/generate.md
BDD patterns: rules/bdd-test-patterns.md