首页 > 解决方案 > 如何在 Flutter 中模拟一个块,并发出状态以响应来自被测小部件的事件

问题描述

我正在尝试测试一个使用 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);
  });
}

标签: flutterflutter-testflutter-bloc

解决方案


我看了看并用我的建议打开了一个拉取请求。我强烈建议您根据通知和反应来考虑您的测试。在这种情况下,我建议进行一项测试,以验证当点击按钮时,是否将正确的事件添加到 bloc(通知 bloc)。然后我建议进行单独的测试,以确保当状态从 FirstState 更改为 SecondState 时呈现正确的页面(UI 对状态更改做出适当的反应)。将来,我强烈建议您查看示例应用程序,因为它们中的大多数都经过了全面测试。


推荐阅读