简体   繁体   English

Flutter Bloc 测试未捕获 thenThrow WebException

[英]Flutter Bloc Testing not catching thenThrow WebException

I'm trying to test the BLoCs in my Flutter App but I hit a wall with this problem below.我正在尝试在我的 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>

which is caused by this Failing BLoC test这是由这个失败的 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>()],
    );

This is the code I have used to test my ChangePasswordBloc (Notice all the other tests are passing successfully)这是我用来测试我的 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>()],
    );
  });
}

This is my ChangePasswordBloc code这是我的 ChangePasswordBloc 代码

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');
    }
  }
}

As you can tell, if a WebException is thrown, I yield ChangePasswordFailure() with an error message.如您所知,如果引发 WebException,我会生成带有错误消息的 ChangePasswordFailure()。 This does work on the actual app, so I am certain that logic works, yet the test does not seem to catch that thrown WebException.这确实适用于实际的应用程序,所以我确信逻辑有效,但测试似乎没有捕捉到抛出的 WebException。

ChangePasswordEvent更改密码事件

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];
}

ChangePasswordState更改密码状态

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];
}

Any suggestions or advice as to why.thenThrow(WebException(403)) is not actually being caught when it actually works on the real Flutter App (if a WebException is thrown, ChangePasswordFailure is always thrown)?当它在真正的 Flutter 应用程序上实际运行时,关于为什么。

I have another example with the same code which does work (The code for ClientInfoBloc is handles WebExceptions in the same way as ChangePasswordBloc and it also works in the real Flutter app)我有另一个使用相同代码的示例(ClientInfoBloc 的代码以与 ChangePasswordBloc 相同的方式处理 WebExceptions,它也适用于真正的 Flutter 应用程序)

Working Test Example with thrown WebException抛出 WebException 的工作测试示例

I checked this related issue but it did not fix anything.我检查了这个相关的问题,但它没有解决任何问题。

I think you need to add another 'when' block to mock this line: final accessToken = await _secureStorage.read(key: 'accessToken');我认为您需要添加另一个“何时”块来模拟这一行: final accessToken = await _secureStorage.read(key: 'accessToken'); something like this: when(secureStorage.read(key: 'accessToken')).thenAnswer(() async => 'token');像这样: when(secureStorage.read(key: 'accessToken')).thenAnswer(() async => 'token'); or remove the parameter 'token' of your current 'when' block.或删除当前“when”块的参数“token”。 Because you are currently saying that when you call the 'changePassword' method with the attribute accessToken = 'token' it will throw an error and that is not true because accessToken = null.因为您当前说的是,当您使用属性 accessToken = 'token' 调用'changePassword' 方法时,它会抛出一个错误,这不是真的,因为 accessToken = null。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM