问题描述
我正在尝试在我的 Flutter 应用程序中测试 BLoC,但我在下面遇到了这个问题。
bool safecopy(byte* out,const byte* from,size_t size) {
const auto pmask = PAGE_READONLY | PAGE_READWRITE | PAGE_WRITEcopY | PAGE_EXECUTE |
PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITEcopY;
auto at = from;
auto till = at + size;
::MEMORY_BASIC_informatION mbi{};
while (at < till) {
if (!VirtualQuery(at,&mbi,sizeof mbi)) return false;
if ((mbi.State == MEM_COMMIT) && !(mbi.Protect & PAGE_GUARD) && (mbi.Protect & pmask)) {
const byte* m_begin = static_cast<const byte*>(mbi.BaseAddress);
const byte* m_end = m_begin + mbi.RegionSize;
at = m_end;
if (m_end > till) m_end = till;
if (m_begin < from) m_begin = from;
memcpy(out + (m_begin - from),m_begin,m_end - m_begin);
} else return false;
}
return true;
}
这是由这个 Failing 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>
这是我用来测试我的 ChangePasswordBloc 的代码(注意所有其他测试都成功通过)
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 代码
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,ChangePasswordSuccess] on successful ChangePassword',)).thenAnswer((_) async => null);
return changePasswordBloc;
},ChangePasswordSuccess(),);
blocTest<ChangePasswordBloc,);
});
}
如您所知,如果抛出 WebException,我会生成 ChangePasswordFailure() 并显示错误消息。这确实适用于实际的应用程序,所以我确信逻辑有效,但测试似乎没有捕捉到抛出的 WebException。
更改密码事件
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');
}
}
}
更改密码状态
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];
}
关于为什么 .thenThrow(WebException(403)) 在真正的 Flutter App 上实际工作时实际上没有被捕获的任何建议或建议(如果抛出 WebException,则总是抛出 ChangePasswordFailure)?
我有另一个相同代码的例子,它确实有效(ClientInfoBloc的代码以与ChangePasswordBloc相同的方式处理WebExceptions,它也适用于真正的Flutter应用程序)
Working Test Example with thrown WebException
解决方法
我认为您需要添加另一个“when”块来模拟这一行:
final accessToken = await _secureStorage.read(key: 'accessToken');
像这样:
when(secureStorage.read(key: 'accessToken')).thenAnswer(() async => 'token');
或删除当前“when”块的参数“token”。
因为您目前是说,当您使用属性 accessToken = 'token' 调用 'changePassword' 方法时,它会抛出错误,而这不是真的,因为 accessToken = null。