J'essaie de créer des tests Espresso et en utilisant un mockWebServer
, le problème est que lorsque j'essaie de créer mon mockWebServer
, il appelle le vrai appel d'API et je veux l'intercepter et me moquer de la réponse.
Mon organisation de poignard est:
Mon appli
open class App : Application(), HasAndroidInjector {
lateinit var application: Application
@Inject
lateinit var androidInjector: DispatchingAndroidInjector<Any>
override fun androidInjector(): AndroidInjector<Any> = androidInjector
override fun onCreate() {
super.onCreate()
DaggerAppComponent.factory()
.create(this)
.inject(this)
this.application = this
}
}
Puis MyAppComponent
@Singleton
@Component(
modules = [
AndroidInjectionModule::class,
AppModule::class,
RetrofitModule::class,
RoomModule::class,
AppFeaturesModule::class
]
)
interface AppComponent : AndroidInjector<App> {
@Component.Factory
interface Factory {
fun create(@BindsInstance application: App): AppComponent
}
}
Ensuite, j'ai créé cette TestApp
class TestApp : App() {
override fun androidInjector(): AndroidInjector<Any> = androidInjector
override fun onCreate() {
DaggerTestAppComponent.factory()
.create(this)
.inject(this)
}
}
Et c'est mon TestAppComponent
@Singleton
@Component(
modules = [
AndroidInjectionModule::class,
AppModule::class,
TestRetrofitModule::class,
AppFeaturesModule::class,
RoomModule::class]
)
interface TestAppComponent : AppComponent {
@Component.Factory
interface Factory {
fun create(@BindsInstance application: App): TestAppComponent
}
}
Remarque: Ici, j'ai créé un nouveau module, appelé TestRetrofitModule
où BASE_URL est " http: // localhost: 808 ", je ne sais pas si j'ai besoin d'autre chose.
J'ai également créé le TestRunner
class TestRunner : AndroidJUnitRunner() {
override fun newApplication(
cl: ClassLoader?,
className: String?,
context: Context?
): Application {
return super.newApplication(cl, TestApp::class.Java.name, context)
}
}
Et mettez-le sur le testInstrumentationRunner
Je ne peux pas utiliser
@Inject
lateinit var okHttpClient: OkHttpClient
car il dit qu'il n'est pas initialisé.
Mon mockWebServer ne distribue pas les réponses même s'il ne pointe pas le vrai appel api, pointe celui que j'ai mis vers le TestRetrofitModule, le fait est que je dois lier ce mockWebServer et Retrofit.
Lorsque j'essaie de faire quelque chose de similaire, je ne crée pas deux types de composants d'application, un seul. Je leur fournit différentes entrées, selon que ce soit pour le App
ou pour le TestApp
. Pas besoin du tout de TestAppComponent
. Par exemple.
open class App : Application(), HasAndroidInjector {
lateinit var application: Application
@Inject
lateinit var androidInjector: DispatchingAndroidInjector<Any>
override fun androidInjector(): AndroidInjector<Any> = androidInjector
override fun onCreate() {
super.onCreate()
DaggerAppComponent.factory()
.create(this, createRetrofitModule())
.inject(this)
this.application = this
}
protected fun createRetrofitModule() = RetrofitModule(BuildConfig.BASE_URL)
}
class TestApp : App() {
override fun createRetrofitModule() = RetrofitModule("http://localhost:8080")
}
@Module
class RetrofitModule(private val baseUrl: String) {
...
provide your Retrofit and OkHttpClients here and use the 'baseUrl'.
...
}
(je ne sais pas si cela 'compile' ou non; j'utilise généralement le motif builder()
sur un composant de poignard, pas le motif factory()
, mais vous voyez l'idée).
Le modèle ici consiste à fournir à votre composant d'application ou à ses modules des entrées pour le 'Edge-of-the-world', des éléments qui doivent être configurés différemment en fonction du contexte dans lequel l'application s'exécuterait (contextes tels que build- saveurs, application exécutée sur un appareil grand public ou exécutée en mode instrumentation, etc.). Des exemples sont les valeurs BuildConfig
(telles que les URL de base pour la mise en réseau), les implémentations d'interface vers du matériel réel ou faux, les interfaces vers des bibliothèques tierces, etc.
Que diriez-vous a dagger module
pour votre Test Class
avec un ContributeAndroidInjector
dedans et faire Inject
sur un @Before
méthode.
Votre TestAppComponent
:
@Component(modules = [AndroidInjectionModule::class, TestAppModule::class])
interface TestAppComponent {
fun inject(app: TestApp)
@Component.Builder
interface Builder {
@BindsInstance
fun application(application: TestApp): Builder
fun build(): TestAppComponent
}
}
TestAppModule
comme:
@Module
interface TestAppModule {
@ContributesAndroidInjector(modules = [Provider::class])
fun activity(): MainActivity
@Module
object Provider {
@Provides
@JvmStatic
fun provideString(): String = "This is test."
}
// Your other dependencies here
}
Et @Before
méthode de Test Class
vous devez faire:
@Before
fun setUp() {
val instrumentation = InstrumentationRegistry.getInstrumentation()
val app = instrumentation.targetContext.applicationContext as TestApp
DaggerTestAppComponent.builder().application(app).build().inject(app)
// Some things other
}
Une chose importante, vous devez avoir (sur build.gradle
module app
):
kaptAndroidTest "com.google.dagger:dagger-compiler:$version_dagger"
kaptAndroidTest "com.google.dagger:dagger-Android-processor:$version"
Maintenant, lorsque vous lancez un Activity
comme MainActivity
, Dagger injectera dependencies
depuis votre TestAppModule
au lieu de AppModule
avant.
De plus, si vous voulez @Inject
à Test Class
, vous pouvez ajouter:
fun inject(testClass: TestClass) // On Your TestAppComponent
Et puis, vous pouvez appeler:
DaggerTestAppComponent.builder().application(app).build().inject(this) // This is on your TestClass
pour Injecter des dépendances dans votre TestClass
.
J'espère que cela peut vous aider !!
Je suppose que vous essayez d'injecter OkHttpClient:
@Inject
lateinit var okHttpClient: OkHttpClient
dans votre classe TestApp, et cela échoue. Afin de le faire fonctionner, vous devrez ajouter une méthode d'injection dans votre TestAppComponent
, pour injecter le TestApp remplacé, de sorte qu'il devienne:
@Singleton
@Component(
modules = [
AndroidInjectionModule::class,
AppModule::class,
TestRetrofitModule::class,
AppFeaturesModule::class,
RoomModule::class]
)
interface TestAppComponent : AppComponent {
@Component.Factory
interface Factory {
fun create(@BindsInstance application: App): TestAppComponent
}
fun inject(testApp: TestApp)
}
La raison pour laquelle cela est nécessaire est que Dagger est basé sur le type et utilise le type de chaque classe à injecter/fournir pour déterminer comment générer le code au moment de la compilation. Dans votre cas, lorsque vous essayez d'injecter le TestApp, le poignard injectera sa superclasse (la classe App), car il sait seulement qu'il doit injecter la classe App. Si vous regardez l'interface AndroidInjector (que vous utilisez dans votre AppComponent), vous verrez qu'elle est déclarée comme:
public interface AndroidInjector<T> {
void inject(T instance)
....
}
Cela signifie qu'il générera une méthode:
fun inject(app App)
dans AppComponent. Et c'est pourquoi @Inject fonctionne dans votre classe App, mais ne fonctionne pas dans votre classe TestApp, sauf si vous l'avez explicitement fourni dans TestAppComponent.