Closed shubhamgarg1 closed 3 months ago
The general guidance when you have multiple implementations is to create an interface and then @Binds
the implementations in modules. For example:
interface Foo
// Production classes
class FooImpl @Inject constructor(...)
@Module
@InstallIn(...)
interface FooModule {
@Binds fun bind(impl: FooImpl): Foo
}
// Test classes
class FooTestImpl @Inject constructor(...)
@Module
@TestInstallIn(components = ..., replaces = FooModule.class)
interface FooTestModule {
@Binds fun bind(impl: FooTestImpl): Foo
}
Yes, this would surely work but needs one to define an interface for that class and ends up allowing one to also inject the implementation directly in parts of the code and that wouldn't get replaced in the test cases. In case, we had a functionality to replace all implementations of a class in all tests created directly using constructor injection, that would be great.
needs one to define an interface for that class
Unless you're just mocking out the behavior, which we only recommend as a last resort (see https://dagger.dev/hilt/testing-philosophy), then you usually want an interface so that you can define two separate implementations. Typically your prod and test implementations are going to be significantly different, e.g. have different dependencies etc.
and ends up allowing one to also inject the implementation directly in parts of the code and that wouldn't get replaced in the test cases
Typically Foo
is public whereas FooImpl
/FooTestImpl
are package-private/internal to prevent others from using the implementation directly. It requires more up-front organization of your code to work properly, but should be a net benefit.
In case, we had a functionality to replace all implementations of a class in all tests created directly using constructor injection, that would be great.
If you still really want to replace a constructor injected class directly, it should already be possible. In Dagger, an @Provides
method will take precedence over an @Inject
constructor class so you just need to add a module with an @Provides
method in your test sources, e.g.
// This is used in production.
class Foo @Inject constructor(...) {...}
// This module is included with your test sources and will override the inject constructor above.
@Module
@InstallIn(SingletonComponent::class)
interface FooTestModule {
@Provides
fun provideFoo(): Foo {...}
}
Thanks a lot. I wasn't aware that @Provides
method takes precedence over @Inject
constructor class. This should help a lot in the use case I was looking to solve with it.
Hilt provides mainly two ways to replace bindings:
1. TestInstallIn
It allows you to replace a module for all test code.
2. BindValue
This helps you to replace a specific binding in a single test case.
If a dependency for a class has been created using Constructor Injection and without any module, there is no way to replace that binding for all the test cases directly.
Can such support be provided in Hilt?