单元测试调用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会更灵活:
- 先在
pubspec.yaml的dev_dependencies里添加依赖:
dev_dependencies: mockito: ^5.4.0 build_runner: ^2.4.0
- 创建一个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 {}
- 运行命令生成mock代码:
flutter pub run build_runner build
- 在测试中使用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
相关产品推荐
相关产品推荐

