颤振块测试

Flutter Bloc Test

我在一个团队中工作,我们正在使用 flutter bloc 进行状态管理,但是我们的一个测试用例根本没有任何意义。集团本身运作良好,但测试没有任何意义。

下面是集团本身。

import 'package:vaccify/features/logout/logout.dart';
import 'package:vaccify/features/side_navigation_bar/domain/get_user_name.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:vaccify/features/side_navigation_bar/domain/get_user_profile_pic_url.dart';

part 'auth_event.dart';
part 'auth_state.dart';

class AuthBloc extends Bloc<AuthEvent, AuthState> {
  GetUserName getUserName;
  GetUserProfilePic getUserProfilePic;
  Logout logout;

  AuthBloc(
      {required this.getUserName,
      required this.logout,
      required this.getUserProfilePic})
      : super(NotLoggedIn()) {
    on<AuthLogIn>(_onLogIn);
    on<AuthLogOut>(_onLogOut);
  }

  void _onLogIn(
    AuthLogIn event,
    Emitter<AuthState> emit,
  ) async {
    final name = await getUserName();
    final profilePic = await getUserProfilePic();
    emit(LoggedIn(name, profilePic));
  }

  void _onLogOut(
    AuthLogOut event,
    Emitter<AuthState> emit,
  ) {
    logout();
    emit(NotLoggedIn());
  }
}

现在进行测试

import 'package:bloc_test/bloc_test.dart';
import 'package:vaccify/core/bloc/authentication/auth_bloc.dart';
import 'package:vaccify/features/logout/logout.dart';
import 'package:vaccify/features/side_navigation_bar/domain/get_user_name.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:vaccify/features/side_navigation_bar/domain/get_user_profile_pic_url.dart';

class MockGetUserName extends Mock implements GetUserName {}
class MockGetProfilePicUrl extends Mock implements GetUserProfilePic {}
class MockLogout extends Mock implements Logout {}

void main() {
  late AuthBloc authBloc;
  late MockGetUserName mockGetUserName;
  late MockLogout mockLogout;
  late MockGetProfilePicUrl mockGetProfilePicUrl;

  setUp(() {
    mockGetUserName = MockGetUserName();
    mockLogout = MockLogout();
    mockGetProfilePicUrl = MockGetProfilePicUrl();

    authBloc = AuthBloc(
        getUserName: mockGetUserName,
        logout: mockLogout,
        getUserProfilePic: mockGetProfilePicUrl);
  });

  /// Contains bloc tests for the [AuthBloc] class
  group(
    'AuthBloc',
    () {
      /// Tests that a [LoggedIn] state occurs when the [AuthLogIn] is called.
      blocTest(
        'login',
        build: () {
          when(() => mockGetUserName()).thenAnswer((_) async => "Hello");
          when(() => mockGetProfilePicUrl()).thenAnswer((_) async => "");
          return authBloc;
        },
        act: (AuthBloc bloc) {
          bloc.add(AuthLogIn());
        },
        expect: () => [isA<LoggedIn>()],
      );

      /// Tests that a [NotLoggedIn] state occurs when the [AuthLogOut] is called.
      blocTest(
        'logout',
        build: () {
          when(() => mockGetUserName()).thenAnswer((_) async => "John Smith");
          when(() => mockGetProfilePicUrl()).thenAnswer((_) async => "");
          when(() => mockLogout()).thenAnswer((_) async {});
          return authBloc;
        },
        act: (AuthBloc bloc) {
          bloc.add(AuthLogIn());
          bloc.add(AuthLogOut());
        },

        expect: () => [
          isA<LoggedIn>(),
          isA<NotLoggedIn>(),
        ],
      );
    },
  );
}

第一个测试 'login' 工作正常并通过,因为我们添加了一个事件 "AuthLogin()" 然后我们期望 LoggedIn

第二个测试 'logout' 是问题,因为我们要添加两个事件 AuthLogin() & AuthLogOut() 因为您必须登录才能登出。 然后我们分别期望 LoggedIn 和 NotLoggedIn 状态。

测试失败并显示以下消息

预期:[<<'LoggedIn'>> 的实例,<<'NotLoggedIn'>> 的实例>>]

实际:['NotLoggedIn' 的实例,'LoggedIn' 的实例]

有趣的是,当我们交换两者时期望测试通过...如下所示

expect: () => [
  isA<NotLoggedIn>(),
  isA<LoggedIn>(),
],

任何建议或指导将不胜感激。

谢谢大家

如果您添加 AuthLogIn 事件只是为了让您的集团为第二个事件做准备,您可以使用 blocTest 中的 seed。像这样:

blocTest(
  'logout',
  build: () {
    when(() => mockLogout()).thenAnswer((_) async {});
    return authBloc;
  },
  seed: () => LoggedIn(),
  act: (AuthBloc bloc) {
    bloc.add(AuthLogOut());
  },
  expect: () => [
    isA<NotLoggedIn>(),
  ],
);

关于您的问题,我认为在添加两个事件 (await Future.delayed(Duration(seconds: 1))) 之间使用延迟可以解决问题。请注意,Bloc 默认情况下同时转换事件,在您的情况下,首先处理注销(它比登录功能完成得更快),然后处理您的登录事件,这导致了问题。