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进行状态管理,并结合了事件监听和依赖注入。当然,你可以根据实际需求进一步扩展和优化这个示例。

