android - 如何在 android 中使用 Dagger 对 kotlin 文件进行 UI 测试?

标签 android unit-testing kotlin mockito ui-testing

下面是我的堆栈跟踪,我已经遍历了所有关于 SO 的问题和答案,但找不到任何解决方案

java.lang.IllegalStateException: Could not initialize plugin: interface 
org.mockito.plugins.MockMaker (alternate: null)
at org.mockito.internal.configuration.plugins.PluginLoader$1.invoke(PluginLoader.java:74)
at java.lang.reflect.Proxy.invoke(Proxy.java:1006)
at $Proxy6.isTypeMockable(Unknown Source)
at org.mockito.internal.util.MockUtil.typeMockabilityOf(MockUtil.java:29)
at org.mockito.internal.util.MockCreationValidator.validateType(MockCreationValidator.java:22)
at org.mockito.internal.creation.MockSettingsImpl.validatedSettings(MockSettingsImpl.java:232)
at org.mockito.internal.creation.MockSettingsImpl.build(MockSettingsImpl.java:226)
at org.mockito.internal.MockitoCore.mock(MockitoCore.java:64)
at org.mockito.Mockito.mock(Mockito.java:1871)
at org.mockito.Mockito.mock(Mockito.java:1780)
at SplashActivityTest.init(SplashActivityTest.kt:126)
at java.lang.reflect.Method.invoke(Native Method) 
at org.junit.rules.TestWatcher$1.evaluate(TestWatcher.java:55)
at org.junit.rules.RunRules.evaluate(RunRules.java:20)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
Caused by: java.lang.NoClassDefFoundError: Failed resolution of: Lnet/bytebuddy/dynamic/loading/ClassInjector$UsingReflection;
at org.mockito.internal.creation.bytebuddy.SubclassInjectionLoader.<init>(SubclassInjectionLoader.java:28)
at org.mockito.internal.creation.bytebuddy.SubclassByteBuddyMockMaker.<init>(SubclassByteBuddyMockMaker.java:33)
at org.mockito.internal.creation.bytebuddy.ByteBuddyMockMaker.<init>(ByteBuddyMockMaker.java:21)
at java.lang.Class.newInstance(Native Method)
Caused by: java.lang.ClassNotFoundException: Didn't find class "net.bytebuddy.dynamic.loading.ClassInjector$UsingReflection" on path: DexPathList[[zip file "/system/framework/android.test.runner.jar", zip file "/system/framework/android.test.mock.jar", zip file "/data/app/com.test-Ceb6_iDz-8wl1a3HhgqEEg==/base.apk", zip file "/data/app/YwRi3yxfA1u5ckInmXjV-A==/base.apk"],nativeLibraryDirectories=[/data/app/test-Ceb6_iDz-8wl1a3HhgqEEg==/lib/x86, /data/app/YwRi3yxfA1u5ckInmXjV-A==/lib/x86, /system/lib]]
at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:134)
at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
at java.lang.ClassLoader.loadClass(ClassLoader.java:312)

下面是我的splashActivityTest,

import android.view.View
import android.view.ViewGroup
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.filters.LargeTest
import androidx.test.rule.ActivityTestRule
import androidx.test.runner.AndroidJUnit4
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.hamcrest.Matchers.allOf
import org.hamcrest.TypeSafeMatcher
import org.hamcrest.core.IsInstanceOf
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@LargeTest
@RunWith(AndroidJUnit4::class)
class SplashActivityTest {

@Rule
@JvmField
var mActivityTestRule = ActivityTestRule(SplashActivity::class.java)
@Rule
@JvmField
val executorRule = TaskExecutorWithIdlingResourceRule()
@Rule
@JvmField
val countingAppExecutors = CountingAppExecutorsRule()
@Rule
@JvmField
val dataBindingIdlingResourceRule = DataBindingIdlingResourceRule(mActivityTestRule)

private lateinit var prefUtils: PrefUtils
private lateinit var navigationController: NavigationController

@Before
fun init() {
    prefUtils = mock()
    navigationController = mock()
}

@Test
fun splashActivityTest() {
    // Added a sleep statement to match the app's execution delay.
    // The recommended way to handle such scenarios is to use Espresso idling resources:
    // https://google.github.io/android-testing-support-library/docs/espresso/idling-resource/index.html
    Thread.sleep(2000)

    val imageView = onView(
        allOf(
            withId(R.id.logo),
            childAtPosition(
                childAtPosition(
                    IsInstanceOf.instanceOf(android.widget.FrameLayout::class.java),
                    0
                ),
                0
            ),
            isDisplayed()
        )
    )
    imageView.check(matches(isDisplayed()))

    val frameLayout = onView(
        allOf(
            childAtPosition(
                childAtPosition(
                    withId(android.R.id.content),
                    0
                ),
                0
            ),
            isDisplayed()
        )
    )
    frameLayout.check(matches(isDisplayed()))

    val frameLayout2 = onView(
        allOf(
            childAtPosition(
                childAtPosition(
                    withId(android.R.id.content),
                    0
                ),
                0
            ),
            isDisplayed()
        )
    )
    frameLayout2.check(matches(isDisplayed()))
}

private fun childAtPosition(
    parentMatcher: Matcher<View>, position: Int
): Matcher<View> {

    return object : TypeSafeMatcher<View>() {
        override fun describeTo(description: Description) {
            description.appendText("Child at position $position in parent ")
            parentMatcher.describeTo(description)
        }

        public override fun matchesSafely(view: View): Boolean {
            val parent = view.parent
            return parent is ViewGroup && parentMatcher.matches(parent)
                    && view == parent.getChildAt(position)
        }
    }
  }
}

实际的 SplashActivity

@OpenForTesting
class SplashActivity : BaseActivity() {

/**
 * Returns layout file ID
 * */
override fun layoutId() = R.layout.activity_splash

/**
 * this method gets called when this activity gets created
 * all tasks those need to be executed when this activity get created
 * */
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    /**
     * TO Load Gif in #ImageView
     */
    Glide.with(this).load(R.raw.new_loading_logo).into(logo)
    /**
     * Handles Timer of 2000 millSeconds to open another Activity
     * prefUtils.isUserLogin() == true -> Opens DashBoard as User is already Logged In
     * else -> Opens Log In Page
     */
    //*while UI test runs, Here when prefUtils.isUserLogin() gets executed See error log below*
    Handler().postDelayed({
        if (prefUtils.isUserLogin())
            navigationController.navigateToDashBoard(this)
        else
            navigationController.navigateToLogin(this)
        finish()
    }, 2000)
  }
}

此 Activity 扩展了具有以下行的 BaseActivity,因此在启动启动后,我的测试失败并且 lateinit var prefUtils 的统计信息尚未初始化,现在为此我使用了模拟但仍然得到 java.lang.IllegalStateException:无法初始化插件:接口(interface) org.mockito.plugins.MockMaker(替代:空)。

@Inject
lateinit var navigationController: NavigationController
@Inject
lateinit var prefUtils: PrefUtils
@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory

我添加了如下依赖,

testImplementation "junit:junit:$junitVersion"
testImplementation "org.mockito:mockito-core:$mockito"
testImplementation "org.mockito:mockito-inline:$mockito"

当我不模拟任何东西时会发生以下错误,

kotlin.UninitializedPropertyAccessException: lateinit property prefUtils has not been initialized
at BaseActivity.getPrefUtils(BaseActivity.kt:41)
at SplashActivity$onCreate$1.run(SplashActivity.kt:38)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

Test running failed: Instrumentation run failed due to 'Process crashed.'

因为 baseActivity 有下面一行 @注入(inject) lateinit var prefUtils:PrefUtils 因此,为了解决这个错误,我正在使用 Mock

现在我也尝试过 power mock 但它在编译时失败了 !!,它指出

Unresolved reference: powermock

在下一行

@RunWith(PowerMockRunner::class)

最佳答案

这里 SplashActivity 继承了 BaseActivity 因此每当测试运行时,注入(inject)的变量仍然未初始化以进行测试因此我通过更改下面的行找到了解决方案

@Rule
@JvmField
var mActivityTestRule = ActivityTestRule(SplashActivity::class.java)

@Rule
@JvmField
var mActivityTestRule = ActivityTestRule(FakeLogInActivity::class.java)

下面是 FakeLogInActivity::class.java 的代码

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.sextpanther.sp.R

/**
* Used for testing fragments inside a fake activity.
*/
class FakeLogInActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_login)
  }
}

现在测试运行成功,但只有带有文本的 snackbar view matcher 测试失败。

关于android - 如何在 android 中使用 Dagger 对 kotlin 文件进行 UI 测试?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53880101/

相关文章:

android - GET 请求 JSONObject android

c# - NUnit 从 ReSharper 与 Visual Studio 2013 获取不同的哈希码值

java - 为什么Spring过滤器总是被调用?

kotlin - Kotlin,执行嵌套的可空性检查时调用了错误的Elvis语句

android - 如何强制应用进入全屏模式(切入友好状态)

android - 通过 bash 脚本获取 Android Studio 项目的构建文件夹路径

android - 带有 BottomSheetBehaviour 的 MotionLayout 中的 RecyclerView 不会在触摸 RecyclerView 时滚动

android - Android Assets 究竟是如何存储在设备的存储空间中的

javascript - 模拟点击文档 ReactJS/JSDom

java - 单元测试注解?