Android Dependency Injection example with dagger. Testing with dependency injection
I noticed that some people on mac have problems running the tests, if so follow these notes to fix the problem. https://github.com/robolectric/robolectric/wiki/Running-tests-in-Android-Studio
class Example{
DB dependency
Example(DB dependency){
this.dependency = dependency;
}
// ...
}
class Example{
DB dependency
void setDB(DB dependency){
this.dependency = dependency;
}
// ...
}
class Example{
DB dependency
}
example.dependency = dependency;
Simply, the Application
has a property Component
. This component (interface) exposes dependencies, for example GithubAPI
. The Component relies on Modules
in order to provide these dependencies. In our example we have a ServiceModule
which creates and provides a GithubAPI to our dependent classes.
In code it looks like this:
@ApplicationScope
@Component(modules = arrayOf(ApplicationModule::class,ServiceModule::class))
interface ApplicationComponent {
val applicationContext: Context
val transformers : Transformers
val githubAPI : GithubAPI
}
@Module
open class ServiceModule {
val URI = "https://api.github.com"
@ApplicationScope
@Provides
open fun provideRestAdapter(): Retrofit {
return Retrofit.Builder().baseUrl(URI).addConverterFactory(
GsonConverterFactory.create()).addCallAdapterFactory(
RxJavaCallAdapterFactory.create()).build()
}
@ApplicationScope
@Provides
open fun provideGithubAPI(retrofit: Retrofit): GithubAPI {
return retrofit.create(GithubAPI::class.java)
}
}
open class ExampleApp : Application() {
val component by lazy { createComponent() }
@Override
open fun createComponent(): ApplicationComponent =
DaggerApplicationComponent.builder()
.serviceModule(ServiceModule())
.applicationModule(ApplicationModule(this))
.build()
}
interface ApplicationComponent
which has ServiceModule
as module @Component(modules = arrayOf(ApplicationModule::class,ServiceModule::class))
to provide GithubAPI, notice that in ServiceModule
the method fun provideGithubAPI(retrofit: Retrofit): GithubAPI
is annotated with @Provides
.Activity
that is depedent on ApplicationComponent
, and you can inject one of the dependencies into this class using one of the three methods explained above annotating it with @Inject
.As explained above we can replace our Module
, so that instead of the production dependency it provides a mocked dependency. All our dependencies are injected through our Application
. So we create a test application, override the createComponent() : ApplicationComponent
function and specify our TestModule
instead of our ServiceModule
.
Application
than defined in your manifest, it will do it automatically for you when you add Test
as a prefix to your Application
name. (TestExampleApp
)So let's do this for our GithubAPI
First we'll create a MockGithubAPI
:
class MockGithubAPI() : GithubAPI {
val repos = listOf(GitHubRepo("test", "www.test.com", "test_desc"))
var throwError = false
override fun getRepos(): Observable<List<GitHubRepo>> {
return if (throwError) Observable.error(Exception("exception")) else Observable.just(repos)
}
}
ServiceModule
to provide an instance of MockGithubAPI instead of the retrofit instance of GithubAPI.@Module
class TestServiceModule : ServiceModule() {
@ApplicationScope
@Provides
override fun provideRestAdapter(): Retrofit {
return Retrofit.Builder().baseUrl(URI).addConverterFactory(
GsonConverterFactory.create()).addCallAdapterFactory(
RxJavaCallAdapterFactory.create()).build()
}
@ApplicationScope
@Provides
override fun provideGithubAPI(retrofit: Retrofit): GithubAPI {
return MockGithubAPI()
}
}
Application
and you now succesfully replaced your depedencies with mocked dependencies.class TestExampleApp : ExampleApp() {
override fun createComponent(): ApplicationComponent = DaggerApplicationComponent.builder().applicationModule(
TestApplicationModule(this)).serviceModule(TestServiceModule()).build()
}
Transfomer
into the Presenter
. You can mock these out to apply Schedulers.immediate()
to keep the AndroidSchedulers.mainThread()
out of your presenter unit test.