You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter测试报错:预期抛出UserNotFoundAuthException却返回Future<AuthUser>

Flutter测试用例失败问题修复

问题核心原因

  1. 异步异常断言方式错误:直接对Future对象使用expect,不会等待Future执行完成,因此无法捕获异步抛出的异常,测试会认为返回的是Future<AuthUser>实例而非预期异常。
  2. 测试未初始化Provider:createUser方法要求Provider必须初始化,否则会抛出NotInitializedException,而非测试预期的用户相关异常。
  3. 异步方法未等待执行:sendVerificationEmail是异步方法,直接调用不等待会导致currentUser未及时更新,断言失败。
  4. 测试数据不匹配:原测试中触发异常的邮箱/密码与logIn方法内的判断条件不符,导致无法抛出预期异常。

修复后的完整代码

import 'package:belunotes/services/auth/auth_exceptions.dart';
import 'package:belunotes/services/auth/auth_provider.dart';
import 'package:belunotes/services/auth/auth_user.dart';
import 'package:test/test.dart';

void main() {
  group('Mock Authentication', () {
    late MockAuthProvider provider;

    // 每个测试前初始化Provider,保证测试独立性
    setUp(() async {
      provider = MockAuthProvider();
      await provider.initialize();
    });

    test('Should not be initialized before setUp', () {
      final freshProvider = MockAuthProvider();
      expect(freshProvider.isInitialized, false);
    });

    test('Cannot log out if not initialized', () {
      final freshProvider = MockAuthProvider();
      expect(
        freshProvider.logOut(),
        throwsA(const TypeMatcher<NotInitializedException>()),
      );
    });

    test('Should be able to be initialized', () {
      expect(provider.isInitialized, true);
    });

    test('User should be null after initialization', () {
      expect(provider.currentUser, null);
    });

    test(
      'Should be able to initialize in less than 2 seconds',
      () async {
        final freshProvider = MockAuthProvider();
        await freshProvider.initialize();
        expect(freshProvider.isInitialized, true);
      },
      timeout: const Timeout(Duration(seconds: 2)),
    );

    test('Create user should delegate to logIn function', () async {
      // 使用expectLater等待异步执行,捕获异常
      await expectLater(
        provider.createUser(email: 'ayushraj1268@gmail.com', password: 'anypassword'),
        throwsA(const TypeMatcher<UserNotFoundAuthException>()),
      );

      await expectLater(
        provider.createUser(email: 'someone@bar.com', password: 'Ayush1268'),
        throwsA(const TypeMatcher<WrongPasswordFoundAuthException>()),
      );

      final user = await provider.createUser(
        email: 'foo',
        password: 'bar',
      );
      expect(provider.currentUser, user);
      expect(user.isEmailVerified, false);
    });

    test('Logged in user should be able to get verified', () async {
      // 先登录获取有效用户
      await provider.createUser(email: 'foo', password: 'bar');
      // 等待异步验证邮件方法执行完成
      await provider.sendVerificationEmail();
      final user = provider.currentUser;
      expect(user, isNotNull);
      expect(user!.isEmailVerified, true);
    });

    test('Should be able to log out and log in again', () async {
      await provider.createUser(email: 'foo', password: 'bar');
      await provider.logOut();
      expect(provider.currentUser, null);

      await provider.logIn(
        email: 'email',
        password: 'password',
      );
      final user = provider.currentUser;
      expect(user, isNotNull);
    });
  });
}

class NotInitializedException implements Exception {}

class MockAuthProvider implements AuthProvider {
  AuthUser? _user;
  var _isInitialized = false;
  bool get isInitialized => _isInitialized;

  @override
  Future<AuthUser> createUser({
    required String email,
    required String password,
  }) async {
    if (!isInitialized) throw NotInitializedException();
    await Future.delayed(const Duration(seconds: 1));
    return logIn(
      email: email,
      password: password,
    );
  }

  @override
  AuthUser? get currentUser => _user;

  @override
  Future<void> initialize() async {
    await Future.delayed(const Duration(seconds: 1));
    _isInitialized = true;
  }

  @override
  Future<AuthUser> logIn({required String email, required String password}) {
    if (!isInitialized) throw NotInitializedException();
    if (email == 'ayushraj1268@gmail.com') throw UserNotFoundAuthException();
    if (password == 'Ayush1268') throw WrongPasswordFoundAuthException();
    const user = AuthUser(isEmailVerified: false);
    _user = user;
    return Future.value(user);
  }

  @override
  Future<void> logOut() async {
    if (!isInitialized) throw NotInitializedException();
    if (_user == null) throw UserNotFoundAuthException();
    await Future.delayed(const Duration(seconds: 1));
    _user = null;
  }

  @override
  Future<void> sendVerificationEmail() async {
    if (!isInitialized) throw NotInitializedException();
    final user = _user;
    if (user == null) throw UserNotFoundAuthException();
    const newUser = AuthUser(isEmailVerified: true);
    _user = newUser;
  }
}

关键修复点说明

  1. 添加setUp初始化逻辑:每个测试前创建新的Provider实例并完成初始化,避免测试间状态污染,同时确保后续测试能正常调用需要初始化的接口。
  2. 异步异常断言改为expectLater:expectLater会等待Future执行完成,正确捕获异步抛出的异常,解决"返回Future实例而非异常"的问题。
  3. 修正测试匹配数据:调整触发异常的邮箱/密码,与logIn方法内的判断条件保持一致,确保抛出预期的异常类型。
  4. 等待异步方法执行:对sendVerificationEmail、logOut等异步方法添加await,确保状态更新完成后再执行断言。
  5. 优化测试独立性:将"未初始化状态"相关测试改为使用新创建的Provider实例,避免与其他测试的初始化状态冲突。

内容的提问来源于stack exchange,提问作者Ayush Raj

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 01:40:09