Flutter响应式编程与Bloc测试插件rx_bloc_test的使用

发布于 1周前 作者 wuwangju 来自 Flutter

Flutter响应式编程与Bloc测试插件rx_bloc_test的使用

rx_bloc_test简介

rx_bloc_test 是一个Flutter包,旨在轻松测试来自 FlutterRxBloc packageRxBlocs。它提供了便捷的方法来验证 RxBloc 在不同事件下的状态变化,确保你的应用程序逻辑正确无误。

CI codecov style license

使用rxBlocTest进行测试

基础用法

你可以通过 rxBlocTest 函数轻松测试特定的 RxBloc 状态。除了测试描述外,它有两个必需参数:buildstate。其中:

  • build 返回需要测试的 RxBloc 实例。
  • state 提供了 bloc 内的具体状态。

此外,还可以通过 expect 参数指定预期的状态值列表,用于对比实际发出的状态值。

rxBlocTest<CounterBloc, int>(
  'Emits [] when created',
  build: () async => CounterBloc(), 
  state: (bloc) => bloc.states.count,
  expect: [],
)

添加事件

如果你想要在初始化之后向 bloc 中添加事件,可以使用 act 参数,它会在 bloc 初始化后执行。

rxBlocTest<CounterBloc, int>(
  'Incrementing value',
  build: () async => CounterBloc(), 
  state: (bloc) => bloc.states.count,
  act: (bloc) async => bloc.events.increment(),
  expect: [1],
)

跳过初始值或指定数量的值

有时你可能不关心某些初始值或者想要跳过一些中间结果,这时可以使用 skip 参数。默认情况下,它会跳过第一个值(即初始值)。如果设置为 0,则不会跳过任何值。

rxBlocTest<DetailsBloc, String>(
  'Fetch third details data',
  build: () async => DetailsBloc(), 
  state: (bloc) => bloc.states.details,
  act: (bloc) async => bloc.events.fetch(),
  wait: Duration(milliseconds:100),
  skip: 3, // This will skip first two values + the initial value
  expect: ['Hello world'],
)

异步等待

对于涉及异步操作的情况,可以利用 wait 参数让测试等待一段时间再继续。

复杂场景下的测试 - rxBlocFakeAsyncTest

对于更复杂的测试案例,尤其是那些包含 throttleTimedebounceTime 的情况,推荐使用 rxBlocFakeAsyncTest。这个函数内部包含了 FakeAsync 实例,可以在不真正等待时间流逝的情况下触发所有计划中的异步事件,从而加快测试速度。

rxBlocFakeAsyncTest<DetailsBloc, String>(
  'Email fake async test bloc',
  build: () => DetailsBloc(repo),
  state: (bloc) => bloc.states.email,
  act: (bloc, fakeAsync) {
    bloc.events.setEmail(' job');
    bloc.events.setEmail(' job@prime');
    bloc.events.setEmail(' job@prime.com ');
    
    fakeAsync.elapse(const Duration(seconds: 3));
  },
  expect: <String>['', 'job@prime.com'],
);

相比之下,如果不使用 FakeAsync,同样的测试可能会花费更多的时间。

rxBlocTest<DetailsBloc, String>(
  'Email test bloc',
  build: () async => DetailsBloc(repo),
  state: (bloc) => bloc.states.email,
  act: (bloc) async {
    bloc.events.setEmail(' job');
    bloc.events.setEmail(' job@prime');
    bloc.events.setEmail(' job@prime.com ');  
  },
  expect: <String>['', 'job@prime.com'],
);

完整示例代码

以下是一个完整的示例代码,展示了如何定义一个简单的 CounterBloc 并对其进行测试。

import 'package:rx_bloc/rx_bloc.dart';
import 'package:rx_bloc_test/rx_bloc_test.dart';
import 'package:rxdart/rxdart.dart';
import 'package:test/test.dart';

void main() {
  group('CounterBloc tests', () {
    rxBlocTest<CounterBloc, int>(
      'Basic rxBlocTest',
      build: () async => CounterBloc(),
      state: (bloc) => bloc.count,
      expect: <int>[0],
    );

    rxBlocTest<CounterBloc, int>(
      'Executing action',
      build: () async => CounterBloc(),
      state: (bloc) => bloc.count,
      act: (bloc) async => bloc.increase(),
      expect: <int>[0, 1],
    );

    rxBlocTest<CounterBloc, int>(
      'Skipping results (skips 0 and 1)',
      build: () async => CounterBloc(),
      state: (bloc) => bloc.count,
      act: (bloc) async {
        bloc
          ..decrease()
          ..decrease();
      },
      skip: 2,
      expect: <int>[-2],
    );
  });
}

class CounterBloc extends RxBlocBase implements RxBlocTypeBase {
  final _loadingCount = BehaviorSubject<int>.seeded(0);

  Stream<int> get count => _loadingCount.stream;

  void increase() => _loadingCount.sink.add(_loadingCount.value + 1);
  void decrease() => _loadingCount.sink.add(_loadingCount.value - 1);

  @override
  void dispose() {
    _loadingCount.close();
    return super.dispose();
  }
}

希望这些信息对你有所帮助!如果有更多问题,请随时提问。


更多关于Flutter响应式编程与Bloc测试插件rx_bloc_test的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter响应式编程与Bloc测试插件rx_bloc_test的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是一个关于如何在Flutter中使用响应式编程框架Bloc,并结合rx_bloc_test插件进行测试的示例。这个示例将展示如何创建一个简单的计数器应用,并对其进行测试。

1. 设置Flutter项目

首先,确保你的Flutter环境已经配置好,并创建一个新的Flutter项目。

flutter create counter_app
cd counter_app

2. 添加依赖

pubspec.yaml文件中添加Bloc和rx_bloc_test的依赖:

dependencies:
  flutter:
    sdk: flutter
  bloc: ^8.0.0  # 确保版本是最新的
  flutter_bloc: ^8.0.0  # 确保版本是最新的

dev_dependencies:
  build_runner: ^2.1.4
  flutter_test:
    sdk: flutter
  rx_bloc_test: ^0.1.0  # 确保版本是最新的

然后运行flutter pub get来安装依赖。

3. 创建Bloc和State

创建一个简单的计数器Bloc和State。

// lib/counter/counter_bloc.dart
import 'package:bloc/bloc.dart';
import 'package:meta/meta.dart';

part 'counter_event.dart';
part 'counter_state.dart';

class CounterBloc extends Bloc<CounterEvent, CounterState> {
  CounterBloc() : super(CounterInitial()) {
    on<CounterIncremented>(_ => add(CounterIncremented()));
  }

  @override
  Stream<CounterState> mapEventToState(
    CounterEvent event,
  ) async* {
    if (event is CounterIncremented) {
      yield* _mapCounterIncrementedToState(currentState);
    }
  }

  Stream<CounterState> _mapCounterIncrementedToState(CounterState state) async* {
    yield state.copy(withIncrement: state.count + 1);
  }
}
// lib/counter/counter_event.dart
part of 'counter_bloc.dart';

abstract class CounterEvent {}

class CounterIncremented extends CounterEvent {}
// lib/counter/counter_state.dart
part of 'counter_bloc.dart';

class CounterState {
  final int count;

  const CounterState({required this.count});

  CounterState copy({int? count}) => CounterState(count: count ?? this.count);

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is CounterState &&
          runtimeType == other.runtimeType &&
          count == other.count;

  @override
  int get hashCode => count.hashCode;
}

class CounterInitial extends CounterState {
  const CounterInitial() : super(count: 0);
}

4. 创建UI

使用Flutter_bloc创建UI组件。

// lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'counter/counter_bloc.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Counter App')),
        body: Center(
          child: BlocProvider(
            create: (context) => CounterBloc(),
            child: CounterView(),
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () => context.read<CounterBloc>().add(CounterIncremented()),
          tooltip: 'Increment',
          child: Icon(Icons.add),
        ),
      ),
    );
  }
}

class CounterView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocBuilder<CounterBloc, CounterState>(
      builder: (context, state) {
        return Text('${state.count}');
      },
    );
  }
}

5. 使用rx_bloc_test进行测试

创建一个测试文件来测试Bloc的行为。

// test/counter_bloc_test.dart
import 'package:bloc_test/bloc_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:rx_bloc_test/rx_bloc_test.dart';
import 'package:test/test.dart';
import 'counter/counter_bloc.dart';

void main() {
  test('CounterBloc increments counter', () => {
    blocTest<CounterBloc, CounterState>(
      build: () => CounterBloc(),
      act: (bloc) => bloc.add(CounterIncremented()),
      expect: () => [
        isA<CounterInitial>(),
        isA<CounterState>().having((state) => state.count, 'count').being(1),
      ],
    );
  });

  test('CounterBloc rx test', () => {
    rxBlocTest<CounterBloc, CounterState>(
      build: () => CounterBloc(),
      initialState: CounterInitial(),
      events: [CounterIncremented()],
      expect: [
        stateWhere((state) => state.count == 1),
      ],
    );
  });
}

6. 运行测试

使用以下命令运行测试:

flutter test

这个示例展示了如何创建一个简单的计数器应用,并使用Bloc进行状态管理,同时使用rx_bloc_test插件进行测试。你可以根据这个示例进一步扩展你的应用,并添加更多的测试。

回到顶部