测试使用导航的 Android 片段

Testing an Android Fragment that uses Navigation

我正在为使用导航 API 的片段编写测试。理想情况下,我想用这样的东西单独测试片段:

@RunWith(AndroidJUnit4::class)
class TestDetailFragment {
    private lateinit var dao: MyDao
    private lateinit var db: MyDatabase
    private lateinit var instrumentation: Instrumentation

    @Before
    fun createDb() {
        val context = ApplicationProvider.getApplicationContext<Context>()
        instrumentation = InstrumentationRegistry.getInstrumentation()
        db = Room.inMemoryDatabaseBuilder(
            context, MyDatabase::class.java).build()
        dao = db.getDao()
    }

    @After
    @Throws(IOException::class)
    fun closeDb() {
        db.close()
    }

    @Test
    fun testCreatePosition() {
        val input = "testing input"
        val entity = MyEntity(1, input)

        launchFragmentInContainer<DetailsFragment>()

        onView(withId(R.id.text_input))
            .perform(
                typeText(input),
                closeSoftKeyboard()
            )
        onView(withId(R.id.save)).perform(click())

        instrumentation.runOnMainSync {
            val liveEntities = dao.getEntities()
            liveEntities .observeForever { entities->
                assertThat(entities.get(0)).isEqualTo(entity)
            }
        }
    }
}

问题是我的保存按钮的 onClick 侦听器使用应用程序的导航控制器 return 到主列表视图:

class DetailsFragment: Fragment() {

    override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.details, container, false)
    }

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

        view.findViewById<FloatingActionButton>(R.id.save).setOnClickListener {
            val db = activity?.let { MyDatabase.getDatabase(it) }!!
            val dao = db.getDao()

            val input = view.findViewById<EditText>(R.id.text_input).text.toString()
            val entity = MyEntity(null, input)
            db.queryExecutor.execute{
                dao.insert(entity )
            }

            findNavController().navigate(R.id.list)
        }
    }
}

现在当我 运行 我的测试时,我得到以下错误:

java.lang.IllegalStateException: View androidx.constraintlayout.widget.ConstraintLayout{42158d7 V.E...... ........ 0,0-1440,2562} does not have a NavController set

这是有道理的,因为我的测试是单独加载片段,而不是使用我的应用程序或导航图中的 activity。经过一点挖掘,我发现 TestNavHostController 并修改我的测试如下:

@RunWith(AndroidJUnit4::class)
class TestDetailFragment {
    private lateinit var dao: MyDao
    private lateinit var db: MyDatabase
    private lateinit var instrumentation: Instrumentation

    @Before
    fun createDb() {
        val context = ApplicationProvider.getApplicationContext<Context>()
        instrumentation = InstrumentationRegistry.getInstrumentation()
        db = Room.inMemoryDatabaseBuilder(
            context, MyDatabase::class.java).build()
        dao = db.getDao()
    }

    @After
    @Throws(IOException::class)
    fun closeDb() {
        db.close()
    }

    @Test
    fun testCreatePosition() {
        val input = "testing input"
        val entity = MyEntity(1, input)

        val detailsScenario = launchFragmentInContainer<DetailsFragment>()

        val navController = TestNavHostController(
            ApplicationProvider.getApplicationContext()
        )
        detailsScenario.onFragment { fragment ->
            navController.setGraph(R.navigation.nav_graph)
            Navigation.setViewNavController(fragment.requireView(), navController)
        }

        onView(withId(R.id.text_input))
            .perform(
                typeText(input),
                closeSoftKeyboard()
            )
        onView(withId(R.id.save)).perform(click())

        instrumentation.runOnMainSync {
            val liveEntities = dao.getEntities()
            liveEntities .observeForever { entities->
                assertThat(entities.get(0)).isEqualTo(entity)
            }
        }
    }
}

这解决了有关缺少 NavController 的错误,但现在我得到了

java.lang.IllegalArgumentException: Navigation action/destination com.example:id/list cannot be found from the current destination NavDestination(com.example:id/ListFragment) label=List Fragment

我该如何从这里开始?我是否为我的测试模拟了 NavDestination?对于旨在断言对象是在数据库中创建的测试来说,这似乎需要付出很多努力。我打算单独测试导航。

我意识到我可能已经深入了解了我的 XY 问题的 Y 路径。最后,我的问题是当 onClick 处理程序需要 NavController 时如何测试我的片段。我的方法是否正确?或者,还有更好的方法?如果没有,我该怎么办?有没有一种好的方法可以修改 onClick 处理程序以不需要直接 NavController ?或者以某种方式注入它?

错误消息说您在 listFragment 目的地,这不是您 list 操作的目的地。

TestNavHostController 有第二种方法 setCurrentDestination() for precisely this reason as explained in the Testing Navigation guide:

TestNavHostController provides a setCurrentDestination method that allows you to set the current destination (and optionally, arguments for that destination) so that the NavController is in the correct state before your test begins.

detailsScenario.onFragment { fragment ->
    navController.setGraph(R.navigation.nav_graph)
    // Use whatever destination ID is associated with your DeatilFragment
    navController.setCurrentDestination(R.id.detail)
    Navigation.setViewNavController(fragment.requireView(), navController)
}