I am writing a test for a fragment that uses the Navigation API. Ideally I would like to test the fragment in isolation with something like this:
@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) } } } } The problem is that the onClick listener for my save button uses the app's nav controller to return to the master list view:
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) } } } Now when I run my test, I get the following error:
java.lang.IllegalStateException: View androidx.constraintlayout.widget.ConstraintLayout{42158d7 V.E...... ........ 0,0-1440,2562} does not have a NavController set This makes sense since my test is loading the fragment in isolation and not using the activity from my app nor the navigation graph. After a little digging, I found TestNavHostController and modified my test as follows:
@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) } } } } This solves the error about a missing NavController, but now I get
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 How do I proceed from here? Do I mock out the NavDestination for my test? This seems like a lot of effort for a test that is intended to assert that the object was created in the database. I plan to test navigation separately.
I realize I'm probably deep down the Y path of my XY problem here. Ultimately, my question is how do I test my fragment when the onClick handler requires a NavController. Is my approach along the right lines? Or is there a better way? If not, what do I do instead? Is there a good way to modify the onClick handler to not require the NavController directly? Or to inject it somehow?
没有评论:
发表评论