我如何在 Flutter 中模拟一个 bloc,并发出状态以响应来自被测小部件的事件
How do I mock a bloc in Flutter, with states being emitted in response to events from a widget under test
我正在尝试测试一个使用 bloc 的小部件。我希望能够从我的模拟集团发出状态,以响应被测小部件触发的事件。我尝试了很多方法都没有成功。我不确定我是否犯了一些简单的错误,或者我是否完全错误地解决了问题。
这是一个演示我的问题的简化项目。 (完整的代码可以在 https://github.com/andrewdixon1000/flutter_bloc_mocking_issue.git 找到)
非常简单的集团
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:meta/meta.dart';
class MyBloc extends Bloc<MyEvent, MyState> {
MyBloc() : super(FirstState());
@override
Stream<MyState> mapEventToState(
MyEvent event,
) async* {
if (event is TriggerStateChange) {
yield SecondState();
}
}
}
@immutable
abstract class MyEvent {}
class TriggerStateChange extends MyEvent {}
@immutable
abstract class MyState {}
class FirstState extends MyState {}
class SecondState extends MyState {}
我正在测试的小部件
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'bloc/my_bloc.dart';
import 'injection_container.dart';
class FirstPage extends StatefulWidget {
const FirstPage({Key? key}) : super(key: key);
@override
_FirsPageState createState() => _FirsPageState();
}
class _FirsPageState extends State<FirstPage> {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => serviceLocator<MyBloc>(),
child: Scaffold(
appBar: AppBar(title: Text("Page 1")),
body: Container(
child: BlocConsumer<MyBloc, MyState>(
listener: (context, state) {
if (state is SecondState) {
Navigator.pushNamed(context, "SECONDPAGE");
}
},
builder: (context, state) {
if (state is FirstState) {
return Column(
children: [
Text("State is FirstState"),
ElevatedButton(
onPressed: () {
BlocProvider.of<MyBloc>(context).add(TriggerStateChange());
},
child: Text("Change state")),
],
);
} else {
return Text("some other state");
}
},
),
),
),
);
}
}
我的插件测试
这就是我挣扎的地方。我正在做的是加载小部件,然后点击按钮。这会导致小部件向集团添加一个事件。我想要做的是让我的模拟集团发出一个状态来响应这个,这样小部件的 BlocConsumer 的侦听器将看到状态改变导航。正如您从代码中的注释中看到的那样,我已经尝试了一些没有运气的事情。目前,我尝试过的任何操作都会导致侦听器看到状态变化。
import 'package:bloc_test/bloc_test.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart' as mocktail;
import 'package:get_it/get_it.dart';
import 'package:test_bloc_issue/bloc/my_bloc.dart';
import 'package:test_bloc_issue/first_page.dart';
class MockMyBloc extends MockBloc<MyEvent, MyState> implements MyBloc {}
class FakeMyState extends Fake implements MyState {}
class FakeMyEvent extends Fake implements MyEvent {}
void main() {
MockMyBloc mockMyBloc;
mocktail.registerFallbackValue<MyState>(FakeMyState());
mocktail.registerFallbackValue<MyEvent>(FakeMyEvent());
mockMyBloc = MockMyBloc();
var nextScreenPlaceHolder = Container();
setUpAll(() async {
final di = GetIt.instance;
di.registerFactory<MyBloc>(() => mockMyBloc);
});
_loadScreen(WidgetTester tester) async {
mocktail.when(() => mockMyBloc.state).thenReturn(FirstState());
await tester.pumpWidget(
MaterialApp(
home: FirstPage(),
routes: <String, WidgetBuilder> {
'SECONDPAGE': (context) => nextScreenPlaceHolder
}
)
);
}
testWidgets('test', (WidgetTester tester) async {
await _loadScreen(tester);
await tester.tap(find.byType(ElevatedButton));
await tester.pump();
// What do I need to do here to mock the state change that would
// happen in the real bloc when a TriggerStateChange event is received,
// such that the listener in my BlocConsumer will see it?
// if tried:
// whenListen(mockMyBloc, Stream<MyState>.fromIterable([SecondState()]));
// and
// mocktail.when(() => mockMyBloc.state).thenReturn(SecondState());
await tester.pumpAndSettle();
expect(find.byWidget(nextScreenPlaceHolder), findsOneWidget);
});
}
我看了一下 opened a pull request with my suggestions. I highly recommend thinking of your tests in terms of notifications and reactions. In this case, I recommend having one test to verify that when the button is tapped, the correct event is added to the bloc (the bloc is notified). Then I recommend having a separate test to ensure that when the state changes from FirstState to SecondState that the correct page is rendered (the UI reacts to state changes appropriately). In the future, I highly recommend taking a look at the example apps 因为大部分都经过了全面测试。
我正在尝试测试一个使用 bloc 的小部件。我希望能够从我的模拟集团发出状态,以响应被测小部件触发的事件。我尝试了很多方法都没有成功。我不确定我是否犯了一些简单的错误,或者我是否完全错误地解决了问题。
这是一个演示我的问题的简化项目。 (完整的代码可以在 https://github.com/andrewdixon1000/flutter_bloc_mocking_issue.git 找到)
非常简单的集团
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:meta/meta.dart';
class MyBloc extends Bloc<MyEvent, MyState> {
MyBloc() : super(FirstState());
@override
Stream<MyState> mapEventToState(
MyEvent event,
) async* {
if (event is TriggerStateChange) {
yield SecondState();
}
}
}
@immutable
abstract class MyEvent {}
class TriggerStateChange extends MyEvent {}
@immutable
abstract class MyState {}
class FirstState extends MyState {}
class SecondState extends MyState {}
我正在测试的小部件
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'bloc/my_bloc.dart';
import 'injection_container.dart';
class FirstPage extends StatefulWidget {
const FirstPage({Key? key}) : super(key: key);
@override
_FirsPageState createState() => _FirsPageState();
}
class _FirsPageState extends State<FirstPage> {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => serviceLocator<MyBloc>(),
child: Scaffold(
appBar: AppBar(title: Text("Page 1")),
body: Container(
child: BlocConsumer<MyBloc, MyState>(
listener: (context, state) {
if (state is SecondState) {
Navigator.pushNamed(context, "SECONDPAGE");
}
},
builder: (context, state) {
if (state is FirstState) {
return Column(
children: [
Text("State is FirstState"),
ElevatedButton(
onPressed: () {
BlocProvider.of<MyBloc>(context).add(TriggerStateChange());
},
child: Text("Change state")),
],
);
} else {
return Text("some other state");
}
},
),
),
),
);
}
}
我的插件测试 这就是我挣扎的地方。我正在做的是加载小部件,然后点击按钮。这会导致小部件向集团添加一个事件。我想要做的是让我的模拟集团发出一个状态来响应这个,这样小部件的 BlocConsumer 的侦听器将看到状态改变导航。正如您从代码中的注释中看到的那样,我已经尝试了一些没有运气的事情。目前,我尝试过的任何操作都会导致侦听器看到状态变化。
import 'package:bloc_test/bloc_test.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart' as mocktail;
import 'package:get_it/get_it.dart';
import 'package:test_bloc_issue/bloc/my_bloc.dart';
import 'package:test_bloc_issue/first_page.dart';
class MockMyBloc extends MockBloc<MyEvent, MyState> implements MyBloc {}
class FakeMyState extends Fake implements MyState {}
class FakeMyEvent extends Fake implements MyEvent {}
void main() {
MockMyBloc mockMyBloc;
mocktail.registerFallbackValue<MyState>(FakeMyState());
mocktail.registerFallbackValue<MyEvent>(FakeMyEvent());
mockMyBloc = MockMyBloc();
var nextScreenPlaceHolder = Container();
setUpAll(() async {
final di = GetIt.instance;
di.registerFactory<MyBloc>(() => mockMyBloc);
});
_loadScreen(WidgetTester tester) async {
mocktail.when(() => mockMyBloc.state).thenReturn(FirstState());
await tester.pumpWidget(
MaterialApp(
home: FirstPage(),
routes: <String, WidgetBuilder> {
'SECONDPAGE': (context) => nextScreenPlaceHolder
}
)
);
}
testWidgets('test', (WidgetTester tester) async {
await _loadScreen(tester);
await tester.tap(find.byType(ElevatedButton));
await tester.pump();
// What do I need to do here to mock the state change that would
// happen in the real bloc when a TriggerStateChange event is received,
// such that the listener in my BlocConsumer will see it?
// if tried:
// whenListen(mockMyBloc, Stream<MyState>.fromIterable([SecondState()]));
// and
// mocktail.when(() => mockMyBloc.state).thenReturn(SecondState());
await tester.pumpAndSettle();
expect(find.byWidget(nextScreenPlaceHolder), findsOneWidget);
});
}
我看了一下 opened a pull request with my suggestions. I highly recommend thinking of your tests in terms of notifications and reactions. In this case, I recommend having one test to verify that when the button is tapped, the correct event is added to the bloc (the bloc is notified). Then I recommend having a separate test to ensure that when the state changes from FirstState to SecondState that the correct page is rendered (the UI reacts to state changes appropriately). In the future, I highly recommend taking a look at the example apps 因为大部分都经过了全面测试。