测试使用导航的 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)
}
我正在为使用导航 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)
}