1

我正在尝试在我的 Flutter 应用程序中测试 BLoC,但我在下面遇到了这个问题。

===== asynchronous gap ===========================
dart:async                                                 _AsyncAwaitCompleter.completeError
package:bloc_test/src/bloc_test.dart                       runBlocTest.<fn>
dart:async                                                 runZoned
package:bloc_test/src/bloc_test.dart 157:9                 runBlocTest
package:bloc_test/src/bloc_test.dart 127:11                blocTest.<fn>

Expected: [
            ChangePasswordLoading:ChangePasswordLoading,
            ChangePasswordFailure:ChangePasswordFailure
          ]
  Actual: [
            ChangePasswordLoading:ChangePasswordLoading,
            ChangePasswordSuccess:ChangePasswordSuccess
          ]
   Which: at location [1] is ChangePasswordSuccess:<ChangePasswordSuccess> instead of ChangePasswordFailure:<ChangePasswordFailure>

package:test_api                             expect
package:bloc_test/src/bloc_test.dart 176:9   runBlocTest.<fn>
===== asynchronous gap ===========================
dart:async                                   _asyncThenWrapperHelper
package:bloc_test/src/bloc_test.dart         runBlocTest.<fn>
dart:async                                   runZoned
package:bloc_test/src/bloc_test.dart 157:9   runBlocTest
package:bloc_test/src/bloc_test.dart 127:11  blocTest.<fn>

这是由这个失败的 BLoC 测试引起的

blocTest<ChangePasswordBloc, ChangePasswordState>(
      'emits [ChangePasswordLoading, ChangePasswordFailure] on failed ChangePassword',
      build: () {
        when(authenticationRepository.changePassword(
          'token',
          'oldPassword',
          'newPassword',
          'newPasswordConfirm',
        )).thenThrow(WebException(403));
        return changePasswordBloc;
      },
      act: (bloc) => bloc
        ..add(ChangePassword(
          oldPassword: 'oldPassword',
          newPassword: 'newPassword',
          newPasswordConfirm: 'newPasswordConfirm',
        )),
      expect: [
        ChangePasswordLoading(),
        ChangePasswordFailure(error: 'Old password is not correct'),
      ],
      errors: [isA<WebException>()],
    );

这是我用来测试我的 ChangePasswordBloc 的代码(注意所有其他测试都成功通过了)

import 'package:bloc_test/bloc_test.dart';
import 'package:flutter_app/business_logic/blocs/change_password/change_password_bloc.dart';
import 'package:flutter_app/business_logic/blocs/change_password/change_password_event.dart';
import 'package:flutter_app/business_logic/blocs/change_password/change_password_state.dart';
import 'package:flutter_app/data/exceptions/web_exception.dart';
import 'package:flutter_app/data/repositories/authentication_repository.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';

class MockAuthenticationRepository extends Mock
    implements AuthenticationRepository {}

class MockSecureStorage extends Mock implements FlutterSecureStorage {}

main() {
  ChangePasswordBloc changePasswordBloc;
  MockSecureStorage secureStorage;
  MockAuthenticationRepository authenticationRepository;

  setUp(() {
    secureStorage = MockSecureStorage();
    authenticationRepository = MockAuthenticationRepository();
    changePasswordBloc = ChangePasswordBloc(
      authenticationRepository,
      secureStorage,
    );
  });

  tearDown(() {
    changePasswordBloc?.close();
  });

  test(
    'initial state is ChangePasswordInitial',
    () => expect(changePasswordBloc.state, ChangePasswordInitial()),
  );

  group('ChangePassword process', () {
    blocTest<ChangePasswordBloc, ChangePasswordState>(
      'emits [ChangePasswordLoading, ChangePasswordSuccess] on successful ChangePassword',
      build: () {
        when(authenticationRepository.changePassword(
          'token',
          'oldPassword',
          'newPassword',
          'newPasswordConfirm',
        )).thenAnswer((_) async => null);
        return changePasswordBloc;
      },
      act: (bloc) => bloc
        ..add(ChangePassword(
          oldPassword: 'oldPassword',
          newPassword: 'newPassword',
          newPasswordConfirm: 'newPasswordConfirm',
        )),
      expect: [
        ChangePasswordLoading(),
        ChangePasswordSuccess(),
      ],
    );

    blocTest<ChangePasswordBloc, ChangePasswordState>(
      'emits [ChangePasswordLoading, ChangePasswordFailure] on failed ChangePassword',
      build: () {
        when(authenticationRepository.changePassword(
          'token',
          'oldPassword',
          'newPassword',
          'newPasswordConfirm',
        )).thenThrow(WebException(403));
        return changePasswordBloc;
      },
      act: (bloc) => bloc
        ..add(ChangePassword(
          oldPassword: 'oldPassword',
          newPassword: 'newPassword',
          newPasswordConfirm: 'newPasswordConfirm',
        )),
      expect: [
        ChangePasswordLoading(),
        ChangePasswordFailure(error: 'Old password is not correct'),
      ],
      errors: [isA<WebException>()],
    );
  });
}

这是我的 ChangePasswordBloc 代码

更改密码块

import 'dart:async';
import 'package:flutter_app/data/exceptions/web_exception.dart';
import 'package:flutter_app/data/repositories/authentication_repository.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:bloc/bloc.dart';
import 'change_password_event.dart';
import 'change_password_state.dart';

class ChangePasswordBloc
    extends Bloc<ChangePasswordEvent, ChangePasswordState> {
  final AuthenticationRepository _authenticationRepository;
  final FlutterSecureStorage _secureStorage;

  ChangePasswordBloc(AuthenticationRepository authenticationRepository,
      FlutterSecureStorage secureStorage)
      : _authenticationRepository = authenticationRepository,
        _secureStorage = secureStorage,
        super(ChangePasswordInitial());

  @override
  Stream<ChangePasswordState> mapEventToState(
    ChangePasswordEvent event,
  ) async* {
    if (event is ChangePassword) {
      yield* _mapChangePasswordToState(event);
    }
  }

  Stream<ChangePasswordState> _mapChangePasswordToState(
      ChangePassword event) async* {
    yield ChangePasswordLoading();
    try {
      final accessToken = await _secureStorage.read(key: 'accessToken');

      await _authenticationRepository.changePassword(
        accessToken,
        event.oldPassword,
        event.newPassword,
        event.newPasswordConfirm,
      );
      yield ChangePasswordSuccess();
    } on WebException catch (e) {
      String errorMessage;
      if (e.statusCode == 422) {
        errorMessage = 'Password must be 8 characters long';
      } else if (e.statusCode == 419) {
        errorMessage = 'New Password is not matching';
      } else if (e.statusCode == 403) {
        errorMessage = 'Old password is not correct';
      }
      yield ChangePasswordFailure(error: errorMessage ?? e.toString());
    } catch (err) {
      yield ChangePasswordFailure(
          error: err.toString() ?? 'An unknown error occurred');
    }
  }
}

如您所知,如果引发 WebException,我会生成带有错误消息的 ChangePasswordFailure()。这确实适用于实际的应用程序,所以我确信逻辑有效,但测试似乎没有捕捉到抛出的 WebException。

更改密码事件

import 'package:equatable/equatable.dart';
import 'package:meta/meta.dart';

abstract class ChangePasswordEvent extends Equatable {
  @override
  List<Object> get props => [];
}

class ChangePassword extends ChangePasswordEvent {
  final String oldPassword;
  final String newPassword;
  final String newPasswordConfirm;

  ChangePassword({
    @required this.oldPassword,
    @required this.newPassword,
    @required this.newPasswordConfirm,
  });

  @override
  List<Object> get props => [oldPassword, newPassword, newPasswordConfirm];
}

更改密码状态

import 'package:meta/meta.dart';
import 'package:equatable/equatable.dart';

abstract class ChangePasswordState extends Equatable {
  @override
  List<Object> get props => [];
}

class ChangePasswordInitial extends ChangePasswordState {}

class ChangePasswordLoading extends ChangePasswordState {}

class ChangePasswordSuccess extends ChangePasswordState {}

class ChangePasswordFailure extends ChangePasswordState {
  final String error;

  ChangePasswordFailure({@required this.error});

  @override
  List<Object> get props => [error];
}

关于为什么 .thenThrow(WebException(403)) 在真正的 Flutter App 上实际工作时实际上没有被捕获的任何建议或建议(如果抛出 WebException,则始终抛出 ChangePasswordFailure)?

我有另一个使用相同代码的示例(ClientInfoBloc 的代码以与 ChangePasswordBloc 相同的方式处理 WebExceptions,它也适用于真正的 Flutter 应用程序)

抛出 WebException 的工作测试示例

我检查了这个相关的问题,但它没有解决任何问题。

4

1 回答 1

0

我认为您需要添加另一个“何时”块来模拟这一行: final accessToken = await _secureStorage.read(key: 'accessToken'); 类似这样: when(secureStorage.read(key: 'accessToken')).thenAnswer(() async => 'token'); 或删除当前“何时”块的参数“令牌”。因为您当前说的是,当您使用属性 accessToken = 'token' 调用 'changePassword' 方法时,它会抛出一个错误,这不是真的,因为 accessToken = null。

于 2021-05-31T09:37:40.277 回答