Comments 1
он будет создаваться всего один раз. А вот сам BLoC мы будет создавать для каждого теста единожды.
```
LoginRepository repository; LoginBloc bloc; setUp(() { repository = MockLoginRepository(); bloc = LoginBloc(repository); });
```
Текст не соответствует коду? В коде и репозиторий и блок создаются для каждого теста. При этом этот код еще и не соответствует коду в гитхабе.
final String? email;
final String? password;
Эти поля повторяются в трех классах? Может быть стоило вынести в какой-то базовый класс? Или миксин? В таком случае код геттера emailStr
можно было бы значительно сократить. Еще момент в том, что во всех состояниях эти поля зануляемые, что странно. Блок как раз и славится тем, что позволяет безболезненно моделировать точные состояния.
extension LoginStateX on LoginState { String? get emailStr { if (this is LoginInitialState || this is LoginSuccessState) { return null; } else if (this is LoginDataState) { return (this as LoginDataState).email; } else if (this is LoginLoadingState) { return (this as LoginLoadingState).email; } else if (this is LoginErrorState) { return (this as LoginErrorState).email; } return null; } }
Конечно вкусовщина, но возможно стоило не создавать расширение, а перенести этот геттер прямо в класс `LoginState`? Кстати, если сохранить this
в локальную переменную, то не придется писать `as LoginDataState`.
act: (_bloc) => _bloc.add(EditedEmail('example@sample.com')),
Обычно для параметров и локальных переменных не используется знак подчеркивания _
в названии, так как они и так приватные.
blocTest( 'emits [LoginDataState] after adding email', build: () => bloc, act: (_bloc) => _bloc.add(EditedEmail('example@sample.com')), expect: () => [ isA<LoginDataState>(), ], );
Разве проверки одного только типа состояния достаточно? А вдруг в нем измененный эмейл не сохранится? Или сохранится не тот? Аналогично и в следующем тесте. Вдруг изменение пароля повлияло на ранее сохраненный эмейл?
Тут мы использовали еще одно свойство blocTest - seed, которое нужно для подстановки изначального состояния в BLoC.
Как то использование seed
выглядит не очень уместным и даже опасным. По хорошему обычно изолируют зависимости, а в логику тестируемого класса извне не лезут. Другими словами лучше взаимодействовать с тестируемой сущностью только доступными публичными методами. Так как сделано ниже.
blocTest( 'emits [LoginErrorState] if email is null', build: () => bloc, act: (_bloc) { _bloc.add(EditedPassword('myPass123')); _bloc.add(LoginButtonPressed()); }, expect: () => [ isA<LoginDataState>(), isA<LoginLoadingState>(), isA<LoginErrorState>(), ], );
По идее `_bloc.add(EditedPassword('myPass123'));`
должно быть в build
, так как это относится к части arrange.
Если мы внимательно посмотрим на код, то увидим, что нужно протестировать следующие кейсы:
Наверное так же стоило проверить случаи когда пароль и/или логин неправильные?
Запустил тесты из репозитория, но один не проходит:
package:matcher expect
package:mocktail/src/mocktail.dart 595:5 VerificationResult.called
test\auth_bloc\login_bloc_test.dart 93:18 main.<fn>.<fn>
package:bloc_test/src/bloc_test.dart 230:21 testBloc.<fn>
===== asynchronous gap ===========================
dart:async _Completer.completeError
package:bloc_test/src/bloc_test.dart 257:43 _runZonedGuarded.<fn>
===== asynchronous gap ===========================
dart:async _CustomZone.registerBinaryCallback
package:bloc_test/src/bloc_test.dart 254:5 _runZonedGuarded.<fn>
dart:async runZonedGuarded
package:bloc_test/src/bloc_test.dart 253:3 _runZonedGuarded
package:bloc_test/src/bloc_test.dart 200:11 testBloc
package:bloc_test/src/bloc_test.dart 156:13 blocTest.<fn>
Expected: <1>
Actual: <3>
Unexpected number of calls
Печалька...
if (state.emailStr?.isNotEmpty == false || state.emailStr?.isNotEmpty == false) { emit(LoginErrorState( email: state.emailStr, password: state.passwordStr, errorToShow: 'Email or password is empty', )); return; }
Дважды на пустоту проверяется email, а пароль нет? Странно, что ваши тесты это не отловили. Возможно вам стоит посмотреть в сторону TDD?
Тестирование BLoC