【问题标题】:Dagger 2 - Inject fields in activityDagger 2 - 在活动中注入字段
【发布时间】:2020-05-29 16:57:30
【问题描述】:

在开始之前,我已经阅读了很多教程,但每个教程都包含有关旧匕首的信息 - 使用现在已弃用的 @builder。我正在使用@Factory

我有什么?

class LoginActivity : AppCompatActivity() {

@Inject
lateinit var authService: AuthService

override fun onCreate(savedInstanceState: Bundle?) {
    AndroidInjection.inject(this)
....
}
}
//----------------
@Singleton
@Component(modules = [TestAppModule::class])
interface TestApplicationComponent : AndroidInjector<TestMyApplication> {
    @Component.Factory
    abstract class Builder : AndroidInjector.Factory<TestMyApplication>
}
//----------------
class TestMyApplication : MyApplication() {
    override fun onCreate() {
        super.onCreate()
        JodaTimeAndroid.init(this)
    }
    override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
        return DaggerTestApplicationComponent.factory().create(this)
    }
}
//----------------
@Singleton
open class AuthService @Inject constructor(
    @AppContext val context: Context, private val authRemoteDataSource: AuthRemoteDataSource
) {
...
}
//----------------
class MockRunner : AndroidJUnitRunner() {
    override fun onCreate(arguments: Bundle?) {
        StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder().permitAll().build())
        super.onCreate(arguments)
    }

    override fun newApplication(cl: ClassLoader?, className: String?, context: Context?): Application {
        return super.newApplication(cl, TestMyApplication::class.qualifiedName, context)
    }
}

注意事项

  1. 我告诉你,AuthService 中的构造函数,因为它有超过 0 个参数
  2. 模拟跑步者应用我的TestMyApplication

和测试类

@RunWith(AndroidJUnit4::class)
    class LoginActivityTest {

@Mock
lateinit var mockAuthService: AuthService

@Rule
@JvmField
val activityRule = ActivityTestRule<LoginActivity>(LoginActivity::class.java, false, false)

@Before
fun beforeEach() {
    MockitoAnnotations.initMocks(this)
    Mockito.doReturn(NOT_SIGNED).`when`(mockAuthService).getUserSignedStatus(ArgumentMatchers.anyBoolean())
    println(mockAuthService.getUserSignedStatus(true)) //test
}

@Test
fun buttonLogin() {
    activityRule.launchActivity(Intent())
    onView(withText("Google")).check(matches(isDisplayed()));
}
}

我想要什么? - 以最简单的方式将模拟的AuthService 附加到LoginActivity

我得到了什么?错误:

调用方法时:android.content.Context.getSharedPreferences 排队:

Mockito.doReturn(NOT_SIGNED).`when`(mockAuthService).getUserSignedStatus(ArgumentMatchers.anyBoolean())

方法getSharedPreferences在真实方法getUserSignedStatus中被调用。 所以现在,我收到一个错误,因为Mockito.when 调用了公开的真实函数。我认为,第二个问题将是嘲笑AuthService 没有注入LoginActivity

【问题讨论】:

  • @sonnet 确实有用的链接,但我再问你一个,如果我创建@Binds abstract fun provideFakeAuth(fake: LoginActivityTest.FakeAuthService): AuthService,有没有一种简单的方法可以在其他测试中使用其他FakeAuthService?或者我应该从头开始创建整个模块?
  • 如果我在这里提供模拟并在接下来的测试中更改方法的返回,也许最好的办法是,但是如何? -> 如何提供 mock 并在测试中获取它?

标签: android dependency-injection mockito junit4 dagger-2


【解决方案1】:

因此,您可能应该通过一个模块提供AuthService,一个用于普通应用程序,一个用于提供模拟版本的android 测试。这意味着从 AuthService 类中删除 Dagger 注释。我不使用Component.Factory,但这个例子应该足以让你用作指导。

androidTest 文件夹中:

创建测试模块:

    // normal app should include the module to supply this dependency
    @Module object AndroidTestModule {

        val mock : AuthService = Mockito.mock(AuthService::class.java)

        @Provides
        @Singleton
        @JvmStatic
        fun mockService() : AuthService =  mock

    }

创建测试组件:

@Component(modules = [AndroidTestModule::class])
@Singleton
interface AndroidTestComponent : AndroidInjector<AndroidTestApp> {

    @Component.Builder interface Builder {

        @BindsInstance fun app(app : Application) : Builder

        fun build() : AndroidTestComponent
    }
}

创建测试应用:

class AndroidTestApp : DaggerApplication() {

    override fun onCreate() {
        super.onCreate()

        Timber.plant(Timber.DebugTree())
    }

    override fun applicationInjector(): AndroidInjector<out DaggerApplication> =
            DaggerAndroidTestAppComponent.builder().app(this).build()
}

然后是跑步者:

class AndroidTestAppJunitRunner : AndroidJUnitRunner() {

    override fun newApplication(cl: ClassLoader?, className: String?, context: Context?): Application {
        return super.newApplication(cl, AndroidTestApp::class.java.canonicalName, context)
    }
}

包含在 Gradle 中的 android 闭包中:

testInstrumentationRunner "com.package.name.AndroidTestAppJunitRunner"

添加这些部门:

kaptAndroidTest "com.google.dagger:dagger-compiler:$daggerVersion"
kaptAndroidTest "com.google.dagger:dagger-android-processor:$daggerVersion"

androidTestImplementation "org.mockito:mockito-android:2.27.0"
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test:rules:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

然后是测试:

@RunWith(AndroidJUnit4::class) class LoginActivityTest {

    @Rule
    @JvmField
    val activityRule = ActivityTestRule<LoginActivity>(LoginActivity::class.java, false, false)

    @Before
    fun beforeEach() {
Mockito.doReturn(NOT_SIGNED).`when`(AndroidTestModule.mock).getUserSignedStatus(ArgumentMatchers.anyBoolean()
    }

    @Test
    fun buttonLogin() {
        activityRule.launchActivity(Intent())
        onView(withText("Google")).check(matches(isDisplayed()));
    }
}

然后,您的依赖项将通过生成的测试组件图提供给 LoginActivity

【讨论】:

  • 我可以在之前的测试中获取 Mocked AuthService,活动开始吗?
  • No call rule.activity.authService BEFORE activityRule.launchActivity(Intent()) 将是 NPE,因为它是通过 AndroidInject.(this) 行在 onCreate 上提供的,但之后就可以了。如果您在模块本身中创建模拟的单个实例,则可以直接通过模块访问模拟
  • 更新以显示如何在活动启动之前访问模拟
  • 对,我也这样做了!干得好 :) 谢谢
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-02-23
  • 1970-01-01
  • 2017-05-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多