I am trying to use dependency injection with get_it
package, in order to make unit tests.
I am using these dependencies :
get_it: ^7.6.0
injectable_generator: ^2.1.6
My main
function looks like this :
void main(List<String> args) async {
// Configure injection
await setup(Env.test);
await getIt.allReady();
// [...]
}
I wait for the setup with the test
environment, and I wait for it to be allReady
.
The file uses injection.dart
, which contains the setup()
function :
GetIt getIt = GetIt.instance;
@InjectableInit(preferRelativeImports: false)
setup(String env) {
getIt.init();
if (env == Env.test) {
getIt.registerLazySingleton<IAuthRepository>(() => TestAuthRepository());
} else {
getIt.registerLazySingleton<IAuthRepository>(() => AuthRepository());
}
}
abstract class Env {
// static const dev = 'dev';
static const prod = 'prod';
static const test = 'test';
}
As you can see, I want to use AuthRepository
for production (will call a database), or TestAuthRepository
for unit tests (will not call the database).
I declared theses class here, in a file auth_repository.dart
:
abstract class IAuthRepository {
Future<int> createNewUser({
required String login,
required String email,
required String password,
});
}
@Injectable(as: IAuthRepository, env: [Env.prod])
class AuthRepository extends BaseRepository implements IAuthRepository {
@override
Future<int> createNewUser({
required String login,
required String email,
required String password,
}) async {
try {
final connection = await getDatabaseConnection();
// ... (make the database call) ...
return 1;
} catch (exception) {
print(exception);
return -1;
}
}
}
@Injectable(as: IAuthRepository, env: [Env.test])
class TestAuthRepository extends BaseRepository implements IAuthRepository {
@override
Future<int> createNewUser({required String login, required String email, required String password}) {
return Future.value(2);
}
}
For now, the testing function createNewUser
will only return 2.
I am launching my test here :
void main() {
AuthController authController = AuthController();
test('Registration success', () async {
int response = await authController.register(
login: 'test',
email: 'email@mail.com',
password: 'Testing193!',
passwordConfirmation: 'Testing193!',
);
expect(response, 1);
});
// ...
}
This test calls for authController.register()
, which is here :
Future<int> register({
required String login,
required String email,
required String password,
required String passwordConfirmation,
}) async {
// [...]
// IAuthRepository authRepository = getIt<IAuthRepository>();
IAuthRepository authRepository = getIt.get<IAuthRepository>();
return await authRepository.createNewUser(
login: login,
email: email,
password: password,
);
}
I try to retrieve an IAuthRepository
instance with getIt
.
However, when I am launching my tests, I get this error :
00:00 +0 -1: Registration success [E]
Bad state: GetIt: Object/factory with type IAuthRepository is not registered inside GetIt.
(Did you accidentally do GetIt sl=GetIt.instance(); instead of GetIt sl=GetIt.instance;
Did you forget to register it?)
package:get_it/get_it_impl.dart 12:19 throwIfNot
package:get_it/get_it_impl.dart 395:5 _GetItImplementation._findFactoryByNameAndType
package:get_it/get_it_impl.dart 423:29 _GetItImplementation.get
package:toast/controllers/auth_controller.dart 31:44 AuthController.register
test/controllers/auth_controller_test.dart 23:41 main.<fn>
I thought I registered my IAuthRepository
class, however it does not work. How can I register it ?
I finally made it work.
Here was the problem : The setup()
function, which registered all the class implementations I needed, was called in the main()
function (yes, that IS silly from me...).
However, as unit tests are launched specifically to each class, the main()
function was never invoked.
So, my test now looks like this :
void main() {
setUpAll(() {
setupGetIt(Env.test);
});
tearDownAll(() {
unregisterGetIt();
});
AuthController authController = AuthController();
test('Registration success', () async {
int response = await authController.register(
login: 'test',
email: 'email@mail.com',
password: 'Testing193!',
passwordConfirmation: 'Testing193!',
);
expect(response, 1);
});
}
The setupGetIt()
function is the same as setup()
(I renamed it to be more clear), it takes care of registering every class I need.
I call it in a setUpAll()
function, in order to register it before all the tests.
Note that I also added a unregisterGetIt()
function, which basically only calls getIt.reset()
to unregister every registered class. It is called in tearDownAll()
, which is called after all tests.
I also removed all the @Injectable()
annotation on top of my implemented class, as they are not needed.