Flutter状态管理插件getbloc_test的使用
Flutter状态管理插件getbloc_test的使用
单元测试与testController
testController
创建一个新的特定于 controller
的测试用例,通过给定的 description
。testController
将处理断言 controller
在执行 act
后是否按顺序发出 expect
预期的状态。testController
还会确保在评估 expect
之前关闭 controller
流以防止额外的状态被发出。
build
应该用于所有controller
的初始化和准备,并且必须返回正在测试的controller
。seed
是一个可选函数,它返回一个状态,在调用act
之前用来填充controller
。act
是一个可选回调,它将在测试用例中被调用,并且应该用于与controller
交互。skip
是一个可选整数,可以用来跳过任意数量的状态。默认值为 0。wait
是一个可选的Duration
,可以在测试controller
中的异步操作时使用,例如debounceTime
。expect
是一个可选函数,它返回一个Matcher
,用于测试controller
是否在act
执行后发出预期的状态。verify
是一个可选回调,可以在expect
之后调用,用于进行额外的验证/断言。verify
调用时会传入由build
返回的controller
。errors
是一个可选函数,它返回一个Matcher
,用于测试controller
在act
执行后是否抛出预期的异常。
示例代码
group('CounterController', () {
testController(
'emits [] when nothing is added',
build: () => CounterController(),
expect: () => [],
);
testController(
'emits [1] when CounterEvent.increment is added',
build: () => CounterController(),
act: (controller) => controller.add(CounterEvent.increment),
expect: () => [1],
);
});
带有种子状态的示例
testController(
'CounterController emits [10] when seeded with 9',
build: () => CounterController(),
seed: () => 9,
act: (controller) => controller.increment(),
expect: () => [10],
);
跳过状态的示例
testController(
'CounterController emits [2] when CounterEvent.increment is added twice',
build: () => CounterController(),
act: (controller) => controller..add(CounterEvent.increment)..add(CounterEvent.increment),
skip: 1,
expect: () => [2],
);
等待异步操作的示例
testController(
'CounterController emits [1] when CounterEvent.increment is added',
build: () => CounterController(),
act: (controller) => controller.add(CounterEvent.increment),
wait: const Duration(milliseconds: 300),
expect: () => [1],
);
验证内部控制器功能的示例
testController(
'CounterController emits [1] when CounterEvent.increment is added',
build: () => CounterController(),
act: (controller) => controller.add(CounterEvent.increment),
expect: () => [1],
verify: (_) {
verify(() => repository.someMethod(any())).called(1);
}
);
期望异常的示例
testController(
'CounterController throws Exception when null is added',
build: () => CounterController(),
act: (controller) => controller.add(null),
errors: () => [isA<Exception>()]
);
注意事项
当使用 testController
与未重写 ==
和 hashCode
的状态类时,您可以提供一个匹配器的 Iterable
而不是显式的状态实例。
testController(
'emits [StateB] when MyEvent is added',
build: () => MyController(),
act: (controller) => controller.add(MyEvent()),
expect: () => [isA<StateB>()],
);
完整示例Demo
以下是一个完整的示例,展示了如何使用 getbloc_test
插件来测试一个简单的计数器控制器。
项目结构
lib/
main.dart
counter_page.dart
counter_controller.dart
counter_event.dart
counter_binding.dart
main.dart
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:getbloc/getbloc.dart';
import 'package:getbloc_test/getbloc_test.dart';
/// A Mocked Counter Controller
class MockCounterController extends MockController<CounterEvent, int>
implements CounterController {}
/// A Stub of Counter Event
class FakeCounterEvent extends Fake implements CounterEvent {}
void main() {
late CounterController counterController;
setUpAll(() {
registerFallbackValue<CounterEvent>(FakeCounterEvent());
});
setUp(() {
counterController = Get.put(MockCounterController());
});
tearDown(Get.reset);
testWidgets('renders a counter 5', (WidgetTester tester) async {
counterController.whenState(5);
await tester.pumpWidget(GetMaterialApp(home: CounterPage()));
expect(find.text('5'), findsOneWidget);
});
testWidgets('renders a counter 10 and call increment event',
(WidgetTester tester) async {
counterController.whenState(10);
await tester.pumpWidget(GetMaterialApp(home: CounterPage()));
await tester.tap(find.byTooltip('increment'));
expect(find.text('10'), findsOneWidget);
verify(() => counterController.add(Increment())).called(1);
});
testWidgets('renders a counter -1 and call decrement event',
(WidgetTester tester) async {
counterController.whenState(-1);
await tester.pumpWidget(GetMaterialApp(home: CounterPage()));
await tester.tap(find.byTooltip('decrement'));
expect(find.text('-1'), findsOneWidget);
verify(() => counterController.add(Decrement())).called(1);
});
}
counter_page.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'counter_controller.dart';
/// A [StatelessWidget] which uses:
/// * [get](https://pub.dev/packages/get)
/// * [getbloc](https://pub.dev/packages/getbloc)
/// to manage the state of a counter.
class App extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return GetMaterialApp(
getPages: [
GetPage<CounterPage>(
name: '/',
page: () => CounterPage(),
binding: CounterBinding(),
),
],
);
}
}
/// Binding class to connect the page with the controller
class CounterBinding extends Bindings {
[@override](/user/override)
void dependencies() {
Get.lazyPut(() => CounterController());
}
}
/// A [StatelessWidget] which demonstrates
/// how to consume and interact with a [CounterController].
class CounterPage extends GetView<CounterController> {
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Counter')),
body: controller.obx(
(counter) => Text(
'$counter',
style: Theme.of(context).textTheme.headline1,
),
),
floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: _buttons,
),
);
}
List<Widget> get _buttons => [
_getButton(
Icons.add,
() => controller.add(Increment()),
'increment',
),
_getButton(
Icons.remove,
() => controller.add(Decrement()),
'decrement',
),
];
Widget _getButton(
IconData iconData, VoidCallback onPressed, String tooltipText) =>
Padding(
padding: const EdgeInsets.all(5.0),
child: FloatingActionButton(
tooltip: tooltipText,
backgroundColor: Get.theme.floatingActionButtonTheme.backgroundColor,
child: Icon(iconData),
onPressed: onPressed,
),
);
}
counter_controller.dart
import 'package:get/get.dart';
import 'counter_event.dart';
/// {[@template](/user/template) counter_controller}
/// A simple [Controller] which manages an `int` as its state.
/// {[@endtemplate](/user/endtemplate)}
class CounterController extends Controller<CounterEvent, int> {
/// {[@macro](/user/macro) counter_controller}
CounterController() : super(0);
[@override](/user/override)
Stream<int> mapEventToState(CounterEvent event) async* {
if (event is Increment) {
yield state + 1;
}
if (event is Decrement) {
yield state - 1;
}
}
}
counter_event.dart
import 'package:equatable/equatable.dart';
/// {[@template](/user/template) counter_event}
/// Event being processed by [CounterController].
/// {[@endtemplate](/user/endtemplate)}
abstract class CounterEvent extends Equatable {
/// {[@macro](/user/macro) counter_event}
const CounterEvent();
[@override](/user/override)
List<Object> get props => [];
}
/// Notifies controller to increment state.
class Increment extends CounterEvent {}
/// Notifies controller to decrement state.
class Decrement extends CounterEvent {}
更多关于Flutter状态管理插件getbloc_test的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter状态管理插件getbloc_test的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是一个关于如何在Flutter项目中使用get_it
和bloc
进行状态管理的示例代码。虽然你提到的是getbloc_test
,但通常我们结合使用get_it
(一个依赖注入容器)和bloc
(一个状态管理库)。这里我们假设你想了解如何使用这两个库来管理Flutter应用的状态。
1. 添加依赖
首先,在你的pubspec.yaml
文件中添加必要的依赖:
dependencies:
flutter:
sdk: flutter
flutter_bloc: ^8.0.0
get_it: ^7.2.0
dev_dependencies:
bloc_test: ^8.0.0
flutter_test:
sdk: flutter
2. 配置get_it
创建一个get_it_instance.dart
文件来配置你的GetIt
容器:
import 'package:get_it/get_it.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
// 定义GetIt实例
final GetIt getIt = GetIt.instance;
void setupLocator() {
// 注册BlocDelegate,用于调试和日志记录(可选)
getIt.registerSingleton<BlocDelegate>(BlocSupervisor.delegate);
// 注册你的Bloc实例(示例)
getIt.registerFactory<CounterBloc>(() => CounterBloc(counterEventSink: getIt<CounterEventSink>()));
getIt.registerFactory<CounterEventSink>(() => CounterEventSink());
}
// 示例事件类
class IncrementEvent {}
// 示例事件接收器(用于依赖注入)
class CounterEventSink {
final _controllers = <VoidCallback>[];
void addListener(VoidCallback listener) {
_controllers.add(listener);
}
void dispatchIncrement() {
for (final listener in _controllers) {
listener();
}
}
}
3. 创建Bloc
创建一个counter_bloc.dart
文件来定义你的Bloc:
import 'package:bloc/bloc.dart';
import 'package:meta/meta.dart';
import 'get_it_instance.dart';
part 'counter_event.dart';
part 'counter_state.dart';
class CounterBloc extends Bloc<CounterEvent, CounterState> {
final Function() counterEventSink;
CounterBloc({required this.counterEventSink}) : super(CounterInitial());
@override
Stream<CounterState> mapEventToState(CounterEvent event) async* {
if (event is IncrementEvent) {
counterEventSink(); // 触发事件接收器
yield CounterIncremented(count: state.count + 1);
}
}
}
4. 定义事件和状态
在counter_bloc.dart
文件中定义事件和状态:
// counter_event.dart
part of 'counter_bloc.dart';
abstract class CounterEvent {}
class IncrementEvent extends CounterEvent {}
// counter_state.dart
part of 'counter_bloc.dart';
abstract class CounterState {}
class CounterInitial extends CounterState {
final int count = 0;
}
class CounterIncremented extends CounterState {
final int count;
CounterIncremented({required this.count});
}
5. 使用BlocProvider和BlocBuilder
在你的主文件(如main.dart
)中使用BlocProvider
和BlocBuilder
:
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'get_it_instance.dart';
import 'counter_bloc.dart';
void main() {
setupLocator();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Flutter Bloc Example')),
body: BlocProvider<CounterBloc>(
create: (context) => getIt<CounterBloc>(),
child: MyHomePage(),
),
),
);
}
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: BlocBuilder<CounterBloc, CounterState>(
builder: (context, state) {
if (state is CounterInitial || state is CounterIncremented) {
return Text('You have pushed the button this many times: ${state.count}');
}
return Text('Unknown state');
},
),
);
}
}
6. 触发事件
在你的UI中触发Bloc事件,例如在按钮点击时:
class MyHomePage extends StatelessWidget {
final CounterEventSink counterEventSink = getIt<CounterEventSink>();
@override
Widget build(BuildContext context) {
counterEventSink.addListener(() {
context.read<CounterBloc>().add(IncrementEvent());
});
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
BlocBuilder<CounterBloc, CounterState>(
builder: (context, state) {
if (state is CounterInitial || state is CounterIncremented) {
return Text('You have pushed the button this many times: ${state.count}');
}
return Text('Unknown state');
},
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
getIt<CounterEventSink>().dispatchIncrement();
},
child: Text('Increment'),
),
],
),
);
}
}
注意:在上面的代码中,我们通过getIt<CounterEventSink>().dispatchIncrement()
来触发事件,这实际上会调用addListener
中注册的回调,从而触发Bloc的状态更新。
这个例子展示了如何使用get_it
和bloc
进行状态管理,并结合了事件监听和依赖注入。当然,你可以根据实际需求进一步扩展和优化这个示例。