我正在尝试在我的 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 应用程序)
我检查了这个相关的问题,但它没有解决任何问题。