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

单元测试调用Supabase.instance前未初始化的问题排查

Cubit单元测试遇Supabase未初始化报错的解决办法

问题背景

给Cubit的setIsLeadingState函数写单元测试时,触发了Supabase未初始化的断言错误,报错提示必须先初始化Supabase实例才能调用Supabase.instance。项目里Supabase负责谷歌OAuth认证、令牌管理和数据库存储。

测试代码

void main() {
  late MockTeamHomeRepository mockTeamHomeRepository;
  late TeamHomeCubit teamHomeCubit;

  group("getTeamHomeData()", () {
    setUp(() async {
      mockTeamHomeRepository = MockTeamHomeRepository();
      teamHomeCubit = TeamHomeCubit(AuthRepository(Supabase.instance),
          TeamRepository(), UserRepository(Supabase.instance));
    });

    blocTest<TeamHomeCubit, TeamHomeState>(
      'Cubit should emit the state that changes isLeading to true.',
      build: () => teamHomeCubit,
      act: (cubit) => cubit.setIsLeadingState(true),
      expect: () => [const TeamHomeState(isLeading: true)],
    );
  });
}

错误信息

_AssertionError ('package:supabase_flutter/src/supabase.dart': Failed assertion: line 32 pos 7: '_instance._initialized': You must initialize the supabase instance before calling Supabase.instance)

尝试过的无效方案

我尝试写了Fake Supabase类,但没法集成到测试代码里,错误依然存在:

import 'package:flutter_test/flutter_test.dart';
import 'package:supabase/supabase.dart';

void main() {
  test('Fake Supabase', () async {
    final supabase = FakeSupabase();

    final response = await supabase.auth.signInWithPassword(
      email: 'test@gmail.com',
      password: 'passsword',
    );
    expect(response.session, isA<Session>());
  });
}

class FakeSupabase extends Fake implements SupabaseClient {
  @override
  get auth => FakeGotrue();
}

class FakeGotrue extends Fake implements GoTrueClient {
  final _user = User(
    id: 'id',
    appMetadata: {},
    userMetadata: {},
    aud: 'aud',
    createdAt: DateTime.now().toIso8601String(),
  );
  @override
  Future<AuthResponse> signInWithPassword(
      {String? email,
      String? phone,
      required String password,
      String? captchaToken}) async {
    return AuthResponse(
      session: Session(
        accessToken: '',
        tokenType: '',
        user: _user,
      ),
      user: _user,
    );
  }
}

解决思路

问题出在你的仓库类(AuthRepository、UserRepository)硬编码依赖了Supabase.instance单例,测试时没法替换成fake实例。正确的做法是通过构造函数注入SupabaseClient,让测试可以灵活传入模拟的实例。

步骤1:改造仓库的构造函数

把仓库类改成依赖SupabaseClient参数,而不是直接用Supabase.instance:

// AuthRepository示例
class AuthRepository {
  final SupabaseClient _supabase;

  // 构造函数接收SupabaseClient
  AuthRepository(this._supabase);

  // 原来用Supabase.instance的地方,全部替换成_supabase
  // 比如获取当前用户的方法:
  Future<User?> getCurrentUser() async {
    return _supabase.auth.currentUser;
  }
}

// UserRepository同理改造
class UserRepository {
  final SupabaseClient _supabase;

  UserRepository(this._supabase);

  // ...其他业务方法
}

步骤2:修改测试代码,注入Fake SupabaseClient

现在可以把你写的Fake Supabase实例注入到仓库里,或者用Mockito生成更灵活的Mock类。

方案一:复用已有的Fake类

把Fake类移到测试文件顶部,然后在setUp中注入:

void main() {
  late TeamHomeCubit teamHomeCubit;
  late FakeSupabase fakeSupabase;

  group("setIsLeadingState()", () {
    setUp(() {
      fakeSupabase = FakeSupabase();
      // 给仓库传入fake的SupabaseClient
      teamHomeCubit = TeamHomeCubit(
        AuthRepository(fakeSupabase),
        TeamRepository(), // 如果这个仓库也依赖Supabase,同样传入fake
        UserRepository(fakeSupabase),
      );
    });

    blocTest<TeamHomeCubit, TeamHomeState>(
      'Cubit should emit state with isLeading=true',
      build: () => teamHomeCubit,
      act: (cubit) => cubit.setIsLeadingState(true),
      expect: () => [const TeamHomeState(isLeading: true)],
    );
  });
}

// 你的Fake类
class FakeSupabase extends Fake implements SupabaseClient {
  @override
  get auth => FakeGotrue();
}

class FakeGotrue extends Fake implements GoTrueClient {
  final _user = User(
    id: 'id',
    appMetadata: {},
    userMetadata: {},
    aud: 'aud',
    createdAt: DateTime.now().toIso8601String(),
  );
  @override
  Future<AuthResponse> signInWithPassword(
      {String? email,
      String? phone,
      required String password,
      String? captchaToken}) async {
    return AuthResponse(
      session: Session(
        accessToken: '',
        tokenType: '',
        user: _user,
      ),
      user: _user,
    );
  }
}

方案二:用Mockito生成Mock类(更推荐)

如果需要模拟更多Supabase方法,Mockito会更灵活:

  1. 先在pubspec.yaml的dev_dependencies里添加依赖:
dev_dependencies:
  mockito: ^5.4.0
  build_runner: ^2.4.0
  1. 创建一个mock文件(比如mock_supabase.dart):
import 'package:supabase/supabase.dart';
import 'package:mockito/mockito.dart';

class MockSupabaseClient extends Mock implements SupabaseClient {}
class MockGoTrueClient extends Mock implements GoTrueClient {}
  1. 运行命令生成mock代码:
flutter pub run build_runner build
  1. 在测试中使用Mock:
void main() {
  late TeamHomeCubit teamHomeCubit;
  late MockSupabaseClient mockSupabase;
  late MockGoTrueClient mockGoTrue;

  group("setIsLeadingState()", () {
    setUp(() {
      mockGoTrue = MockGoTrueClient();
      mockSupabase = MockSupabaseClient();
      // 关联mock的auth客户端
      when(mockSupabase.auth).thenReturn(mockGoTrue);

      teamHomeCubit = TeamHomeCubit(
        AuthRepository(mockSupabase),
        TeamRepository(),
        UserRepository(mockSupabase),
      );
    });

    blocTest<TeamHomeCubit, TeamHomeState>(
      'Cubit should emit state with isLeading=true',
      build: () => teamHomeCubit,
      act: (cubit) => cubit.setIsLeadingState(true),
      expect: () => [const TeamHomeState(isLeading: true)],
    );
  });
}

额外优化:直接Mock仓库类

如果setIsLeadingState只是单纯修改Cubit状态,根本不需要调用Supabase的逻辑,那可以直接Mock整个仓库类,完全绕开Supabase的依赖:

// 用Mockito生成仓库的Mock类
class MockAuthRepository extends Mock implements AuthRepository {}
class MockUserRepository extends Mock implements UserRepository {}

// 测试代码
setUp(() {
  teamHomeCubit = TeamHomeCubit(
    MockAuthRepository(),
    MockTeamRepository(),
    MockUserRepository(),
  );
});

这种方式更轻量,适合测试纯状态变更的逻辑。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 04:50:55