【问题标题】:'Null' is not a subtype of type 'Stream<int>' in type cast cubit(Bloc) flutter'Null' 不是类型 cast cubit(Bloc) 颤振中的 'Stream<int>' 类型的子类型
【发布时间】:2022-07-16 23:04:20
【问题描述】:

我在颤振中创建了一个 cubit 测试项目,它运行良好,但是当我用 mockito 编写 UI 测试用例时,它会抛出以下内容错误。 “Null”不是类型转换中“Stream”类型的子类型。如果真实对象通过,则单元测试工作正常。

我的Cubit 我的Cubit = 我的Cubit(); //真实对象与UT正常工作

MyCubit myCubit = MockMyCubit(); //模拟对象不能在 UT 上正常工作。

以前,当我没有升级我的颤振时,相同的代码正在使用 mockito。我也尝试过使用 mockito 来模拟 Stream,但它也没有用。

我的代码如下

颤动依赖项

flutter_bloc: ^8.0.1
mockito: ^5.1.0

my_cubit.dart

class MyCubit extends Cubit<int> {
  MyCubit() : super(0);

  void increment() {
    emit(state + 1);
  }

  void decrement() {
    emit(state - 1);
  }
}

ma​​in.dart

void main() {
  MyCubit myCubit = MyCubit();
  runApp(MyAppParent(myCubit));
}

class MyAppParent extends StatelessWidget {
  MyAppParent(this.myCubit);

  MyCubit myCubit;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('test'),
        ),
        body: BlocProvider<MyCubit>(
          create: (_) => myCubit,
          child: MyApp(),
        ),
      ),
    );
  }
}

class MyApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    MyCubit myCubit = context.read<MyCubit>();
    return Column(
      children: [
        BlocBuilder<MyCubit, int>(bloc: myCubit, builder: (BuildContext context, int count) {
            return Text('$count');
        }),
        TextButton(
          onPressed: () {
            myCubit.increment();
          },
          child: const Text('Increment'),
        ),
        TextButton(
          onPressed: () {
            myCubit.decrement();
          },
          child: const Text('Decrement'),
        )
      ],
    );
  }
}

widget_test.dart

class MockedMyCubit extends Mock implements MyCubit {}

void main() {
  testWidgets('Testing', (WidgetTester tester) async {
    MyCubit myCubit = MockMyCubit(); //fake object is not working, throwing exception
    // when(myCubit.stream).thenAnswer((_)  => StreamController<int>.broadcast().stream);

    // MyCubit myCubit = MyCubit(); //real object working fine
    await tester.pumpWidget(MyAppParent(myCubit));

    Finder finderCount = find.text('0');
    expect(finderCount, findsOneWidget);
    Finder finderIncrement = find.text('Increment');
    Finder finderDecrement = find.text('Decrement');

    await tester.tap(finderIncrement);
    await tester.pump();
    Finder finderCount1 = find.text('1');
    expect(finderCount1, findsOneWidget);

    await tester.tap(finderDecrement);
    await tester.pump();
    Finder finderCount0 = find.text('0');
    expect(finderCount0, findsOneWidget);
  });
}

【问题讨论】:

  • 你为什么要嘲笑肘?腕尺应该是最容易测试的部分......

标签: flutter dart widget-test-flutter flutter-cubit flutter-mockito


【解决方案1】:

我相信这是因为需要告知模拟腕尺的初始状态值。实例化肘后试试这个:

when(() =&gt; myCubit.state).thenReturn(0);

【讨论】:

  • 初始值已在 my_cubit.dart 下作为 0 传递,例如 MyCubit() : super(0);。建议的解决方案也不起作用。
【解决方案2】:

您可以使用名为 mocktail 的库。

MockedMyCubit 的状态类型应通过扩展MockCubit&lt;int&gt; 来提供,如下所示:

import 'package:mocktail/mocktail.dart';

class MockedMyCubit extends MockCubit<int> implements MyCubit {}

记得使用whenwhenListen 定义它的行为方式。

Issue discussion

【讨论】:

    【解决方案3】:

    首先,错误'Null' is not a subtype of type 'Stream' in typecast是因为你没有为你的模拟块指定一个状态/cubit,在这种情况下,是一个初始状态。

    其次,你想测试什么?你想测试你的小部件还是你想测试你的肘?请记住,建议只测试一件事,其余的应该被嘲笑。因此,如果您正在测试您的小部件,那么您应该模拟您的肘部。我知道你已经模拟了你的 cubit 但你没有为你的 cubit 类的增量和减量方法设置任何存根。

    如果您想测试您的小部件,请执行以下操作:

    flutter dependencies(使用 bloc_test 而不是 mockito 来模拟你的 bloc/cubit)

    flutter_bloc: ^8.0.1
    bloc_test: ^9.0.3

    my_cubit.dart(和你的一样)

    class MyCubit extends Cubit<int> {
      MyCubit() : super(0);
    
      void increment() {
        emit(state + 1);
      }
    
      void decrement() {
        emit(state - 1);
      }
    }

    ma​​in.dart(在这里你可以注意到我将 BlocBuilder 替换为 BlocConsumer 以在 >listener回调函数。这可以让你在运行测试时看到cubit状态的变化)

    void main() {
      MyCubit myCubit = MyCubit();
      runApp(MyAppParent(myCubit: myCubit));
    }
    
    class MyAppParent extends StatelessWidget {
      const MyAppParent({
        Key? key,
        required this.myCubit,
      }) : super(key: key);
    
      final MyCubit myCubit;
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            appBar: AppBar(
              title: const Text('test'),
            ),
            body: BlocProvider<MyCubit>(
              create: (_) => myCubit,
              child: const MyApp(),
            ),
          ),
        );
      }
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        MyCubit myCubit = context.read<MyCubit>();
        return Column(
          children: [
            BlocConsumer<MyCubit, int>(
              bloc: myCubit,
              listener: (BuildContext context, int count) {
                print('COUNTER: $count');
              },
              builder: (BuildContext context, int count) {
                return Text('$count');
              },
            ),
            TextButton(
              onPressed: () {
                myCubit.increment();
              },
              child: const Text('Increment'),
            ),
            TextButton(
              onPressed: () {
                myCubit.decrement();
              },
              child: const Text('Decrement'),
            )
          ],
        );
      }
    }

    widget_test.dart(如您在此处所见,我正在使用 bloc_test 提供的 MockCubit 类为 cubit 创建一个模拟,并且我正在提供我想使用 whenListen 存根在我的小部件上测试的状态)

    现在您对 MyAppParentMyApp 小部件具有 100% 的测试覆盖率。

    class MockMyCubit extends MockCubit<int> implements MyCubit {}
    
    void main() {
      late final MyCubit myCubit;
    
      setUpAll(() {
        myCubit = MockMyCubit();
      });
    
      group('Testing', () {
        testWidgets('counter equal to 1', (WidgetTester tester) async {
          whenListen(
            myCubit,
            Stream.fromIterable([0, 1]),
            initialState: 0,
          );
    
          await tester.pumpWidget(MyAppParent(myCubit: myCubit));
    
          // The next three lines allows to test the button exists,
          // it can be omitted and this test will still working
          Finder finderIncrement = find.text('Increment');
          expect(finderIncrement, findsOneWidget);
          await tester.tap(finderIncrement);
    
          await tester.pump();
          Finder finderCount = find.text('1');
          expect(finderCount, findsOneWidget);
        });
      });
    
      group(
        'Testing',
        () {
          testWidgets(
              'counter equal to 0 after incrementing and decrementing, in that order',
              (WidgetTester tester) async {
            whenListen(
              myCubit,
              Stream.fromIterable([0, 1, 0]),
              initialState: 0,
            );
    
            await tester.pumpWidget(MyAppParent(myCubit: myCubit));
    
            // The next three lines allows to test the button exists,
            // it can be omitted and this test will still working
            Finder finderDecrement = find.text('Decrement');
            expect(finderDecrement, findsOneWidget);
            await tester.tap(finderDecrement);
    
            await tester.pump();
            Finder finderCount = find.text('0');
            expect(finderCount, findsOneWidget);
          });
        },
      );
    }

    您可以在控制台输出中看到打印的状态:

    您可以在此处找到整个示例:https://github.com/Abel1027/widget-bloc-testing

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-08-12
      • 2020-09-29
      • 2021-02-25
      • 2023-03-31
      • 2022-08-23
      • 1970-01-01
      • 2021-11-20
      • 2019-08-14
      相关资源
      最近更新 更多