Тест Robolectri c не может найти идентификатор макета кадра, используя FragmentScenario и Databinding - PullRequest
2 голосов
/ 22 января 2020

При надувании макета фрагмента с помощью привязки данных у меня возникает следующая ошибка в моем тесте Robolectri c.

java.lang.IllegalArgumentException: No view found for id 0x7f0a00d9 (com.example.dev:id/profile_sign_in_fragment_container) for fragment ProfileSignInEmailFragment{682914fc (26ff296d-333b-4750-b022-cca0406b779a) id=0x7f0a00d9}

Если я надуваю макет без привязки данных, ошибка исчезает. Любая помощь будет принята с благодарностью!

ProfileSignInFragment.kt

package com.example.auth

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentTransaction
import com.example.R
import com.example.databinding.FragmentProfileSignInBinding

class ProfileSignInFragment : Fragment() {

    lateinit var dataBinding: FragmentProfileSignInBinding

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return FragmentProfileSignInBinding.inflate(inflater, container, false)
                .apply { dataBinding = this }
                .root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        dataBinding.profileSignInEmailSignInButton.setOnClickListener {
            requireFragmentManager()
                    .beginTransaction()
                    .replace(R.id.profile_sign_in_fragment_container, ProfileSignInEmailFragment())
                    .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
                    .addToBackStack(null)
                    .commit()
        }
    }
}

ProfileSignInFragmentTest.kt

package com.example.auth

import androidx.fragment.app.testing.launchFragmentInContainer
import androidx.lifecycle.Lifecycle.State.CREATED
import androidx.lifecycle.Lifecycle.State.RESUMED
import com.example.KoinRule
import com.example.R
import com.example.TestApplication
import com.example.duplicate.screen.clickEmailSignInButton
import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.MatcherAssert.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config

@Config(application = TestApplication::class)
@RunWith(RobolectricTestRunner::class)
class ProfileSignInFragmentTest {

    @Rule
    @JvmField
    var koinRule: KoinRule = KoinRule()

    @Test
    fun `clicking email sign in should navigate to email sign in screen`() {
        val scenario = launchFragmentInContainer<ProfileSignInFragment>()
        scenario.moveToState(RESUMED)
        clickEmailSignInButton()

        scenario.onFragment { fragment ->
            fragment.requireFragmentManager().let { fragmentManager ->
                fragmentManager.executePendingTransactions()
                val targetFragment = fragmentManager.findFragmentById(R.id.profile_sign_in_fragment_container)
                assertThat(targetFragment is ProfileSignInEmailFragment, equalTo(true))
            }
        }
    }
}

ProfileSignInActivity.kt

package com.example.auth

import android.os.Bundle
import com.example.R
import com.example.view.SingleFragmentActivity

const val PROFILE_SIGN_IN = 100

class ProfileSignInActivity : SingleFragmentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.activity_profile_sign_in)

        replaceFragment(ProfileSignInFragment(), R.id.profile_sign_in_fragment_container)
    }

}

TestApplication.kt

package com.example

import android.app.Application

class TestApplication : Application() {

    override fun onCreate() {
        super.onCreate()
        setTheme(R.style.Theme_MaterialComponents_NoActionBar)
    }
}

frag_profile_sign_in. xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fillViewport="true">


        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="64dp">

            <TextView
                android:id="@+id/profile_sign_in_title"
                style="@style/H1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/profile_sign_in_title"
                android:textSize="36sp"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

            <com.google.android.material.button.MaterialButton
                android:id="@+id/profile_sign_in_google_sign_in_button"
                style="@style/ButtonTheme"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginStart="100dp"
                android:layout_marginTop="@dimen/activity_vertical_margin"
                android:layout_marginEnd="100dp"
                android:text="@string/profile_sign_in_google_sign_in_button"
                app:icon="@drawable/ic_icons8_google"
                app:iconSize="18dp"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/profile_sign_in_title" />

            <com.google.android.material.button.MaterialButton
                android:id="@+id/profile_sign_in_email_sign_in_button"
                style="@style/ButtonTheme"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginStart="100dp"
                android:layout_marginTop="@dimen/activity_vertical_margin"
                android:layout_marginEnd="100dp"
                android:text="@string/profile_sign_in_email_sign_in_button"
                app:icon="@drawable/icons8_important_mail"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/profile_sign_in_google_sign_in_button" />

            <com.google.android.material.button.MaterialButton
                android:id="@+id/profile_sign_in_sign_up_button"
                style="@style/Widget.MaterialComponents.Button.TextButton"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="8dp"
                android:text="@string/profile_sign_in_sign_up"

                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/profile_sign_in_email_sign_in_button" />

        </androidx.constraintlayout.widget.ConstraintLayout>

    </ScrollView>
</layout>

activity_profile_sign_in. xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <FrameLayout
        android:id="@+id/profile_sign_in_fragment_container"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1">
    </FrameLayout>

</LinearLayout>

build.gradle

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
apply plugin: 'com.google.gms.google-services'

android {
    compileSdkVersion 29
    buildToolsVersion '29.0.2'
    defaultConfig {
        multiDexEnabled true
        applicationId rootProject.applicationId
        minSdkVersion 21
        targetSdkVersion 29
        versionCode 7
        versionName "1.1.0-beta"
        vectorDrawables.useSupportLibrary = true
        testInstrumentationRunner "com.example.InstrumentationRunner"
    }
    signingConfigs {
        release {
            storeFile file(project.property('example.keystore'))
            storePassword project.property('example.keystore.password')
            keyAlias project.property('example.key.alias')
            keyPassword project.property('example.key.password')
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.release
        }
    }
    flavorDimensions 'environment'
    productFlavors {
        local {
            dimension 'environment'
            applicationId "${rootProject.applicationId}.local"
        }
        dev {
            dimension 'environment'
            applicationId "${rootProject.applicationId}.dev"
        }
        prod {
            dimension 'environment'
        }
    }
    packagingOptions {
        exclude 'META-INF/LICENSE'
    }
    testOptions {
        unitTests {
            includeAndroidResources = true
        }
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    dataBinding {
        enabled = true
    }
}

// TODO Trim down unneeded dependencies
dependencies {
    def work_version = '2.2.0'
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation "androidx.work:work-runtime-ktx:$work_version"
    implementation "androidx.work:work-rxjava2:$work_version"
    implementation 'com.google.android.material:material:1.0.0'
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$rootProject.ext.kotlinVersion"
    implementation "org.jetbrains.kotlin:kotlin-reflect:$rootProject.kotlinVersion"
    implementation "org.koin:koin-android:$rootProject.koinVersion"
    implementation "org.koin:koin-androidx-viewmodel:$rootProject.koinVersion"
    implementation "org.koin:koin-java:$rootProject.koinVersion"
    implementation 'com.google.android.gms:play-services-location:16.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    implementation 'com.squareup.okhttp3:logging-interceptor:3.6.0'
    implementation 'com.squareup.retrofit2:retrofit:2.5.0'
    // TODO Look into more kotlin friendly marshaller than jackson
    implementation 'com.squareup.retrofit2:converter-jackson:2.3.0'
    implementation 'com.squareup.retrofit2:converter-scalars:2.3.0'
    implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
    implementation 'com.mapbox.mapboxsdk:mapbox-android-navigation:0.35.0'
    implementation 'com.mapbox.mapboxsdk:mapbox-android-sdk:8.0.0'
    implementation 'com.jakewharton.timber:timber:4.7.1'
    implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
    def roomVersion = "2.1.0"
    implementation "androidx.room:room-runtime:$roomVersion"
    implementation "androidx.room:room-rxjava2:$roomVersion"
    implementation 'com.mapbox.mapboxsdk:mapbox-android-plugin-offline-v8:0.6.0'
    implementation 'com.google.firebase:firebase-auth:19.2.0'
    implementation 'com.google.android.gms:play-services-auth:17.0.0'
    implementation "androidx.fragment:fragment-ktx:1.1.0"
    debugImplementation 'androidx.fragment:fragment-testing:1.1.0'

    kapt "androidx.room:room-compiler:$roomVersion"

    testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0"
    testImplementation "org.koin:koin-test:$rootProject.koinVersion"
    testImplementation 'junit:junit:4.12'
    testImplementation 'org.robolectric:robolectric:4.3'
    testImplementation 'androidx.test:core:1.2.0'
    testImplementation "androidx.test.espresso:espresso-core:3.2.0"
    testImplementation "androidx.test.espresso:espresso-contrib:3.2.0"
    testImplementation 'android.arch.core:core-testing:1.1.1'

    androidTestImplementation "org.koin:koin-test:$rootProject.koinVersion"
    androidTestImplementation "androidx.test.espresso:espresso-core:3.2.0"
    androidTestImplementation "androidx.test.espresso:espresso-contrib:3.2.0"
    androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
    androidTestImplementation 'androidx.test:runner:1.2.0'
    androidTestImplementation 'androidx.test:rules:1.2.0'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'com.squareup.rx.idler:rx2-idler:0.9.1'
}

1 Ответ

1 голос
/ 24 января 2020

Хорошо, я понял, в чем была моя проблема. Причина, по которой FrameLayout не может быть получен, заключается в том, что я использую FragmentScenario вместо ActivityScenario. FragmentScenario использует другое действие в качестве контейнера для фрагмента и поэтому ничего не знает о FrameLayout. Вот как теперь выглядит правильный тестовый класс.

package com.example.auth

import androidx.fragment.app.testing.launchFragmentInContainer
import androidx.lifecycle.Lifecycle.State.RESUMED
import androidx.test.core.app.ActivityScenario
import com.example.KoinRule
import com.example.R
import com.example.TestApplication
import com.example.duplicate.screen.clickEmailSignInButton
import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.MatcherAssert.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config

@Config(application = TestApplication::class)
@RunWith(RobolectricTestRunner::class)
class ProfileSignInFragmentTest {

    @Rule
    @JvmField
    var koinRule: KoinRule = KoinRule()

    @Test
    fun `clicking email sign in should navigate to email sign in screen`() {
        val scenario = ActivityScenario.launch(ProfileSignInActivity::class.java)
        scenario.moveToState(RESUMED)
        clickEmailSignInButton()

        scenario.onActivity { activity ->
            activity.supportFragmentManager.let { fragmentManager ->
                fragmentManager.executePendingTransactions()
                val targetFragment = fragmentManager.findFragmentById(R.id.profile_sign_in_fragment_container)
                assertThat(targetFragment is ProfileSignInEmailFragment, equalTo(true))
            }
        }
    }
}
...