Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit test Dagger Fragments with FragmentScenario

Tags:

I am trying to test fragment interactions using the Android Jetpack Navigation Component and the fragment-testing library. My app is using java + Dagger2 as DI.

To test the navigation I have created a JUnit test:

    @Test
    public void testNavigationToLoginFragment() {
        // Create a mock NavController
        NavController mockNavController = mock(NavController.class);

        // Create a graphical FragmentScenario for the Intro Fragment
        FragmentScenario<IntroFragment> introFragmentScenario = FragmentScenario.launchInContainer(IntroFragment.class);

        // Set the NavController property on the fragment
        introFragmentScenario.onFragment(fragment ->
                Navigation.setViewNavController(fragment.requireView(), mockNavController)
        );

        // Verify that performing a click prompts the correct Navigation action
        onView(withId(R.id.button_login)).perform(click());
        verify(mockNavController).navigate(R.id.action_intro_fragment_to_login_fragment);
    }

Whenever I run the test I get following error:

java.util.concurrent.ExecutionException: java.lang.IllegalArgumentException: No injector factory bound for Class<XXX>

How can I inject my fragment there? Is it possible to use DaggerFragments with FragmentScenario?

IntroFragment

public class IntroFragment extends DaggerFragment{
    @Inject
    CreateQuoteRecyclerViewAdapter createQuoteRecyclerViewAdapter;

    @Inject
    public ViewModelProvider.Factory factory;

    @inject 
    public MyViewModel viewModel;
    .....

}

MyViewModel.java

class CreateOrSignInViewModel extends BaseViewModel() {
  @Inject
  public  CreateOrSignInBindingState state;


 ......
}
like image 287
Jonas Reif Avatar asked Sep 16 '19 16:09

Jonas Reif


1 Answers

Declare following function within test class:

@Config(application = TestApp::class)
@RunWith(AndroidJUnit4::class)
@LooperMode(LooperMode.Mode.PAUSED)
class MyFragmentTest {

    private val createQuoteRecyclerViewAdapter: CreateQuoteRecyclerViewAdapter = mock()
    private val viewModel: MyViewModel = mock()

    private fun launchFragment(): FragmentScenario<MyFragment> {
      return launchFragmentInContainer(factory = object : FragmentFactory() {
        override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
          return MyFragment().apply {
            createQuoteRecyclerViewAdapter = [email protected]
            viewModel = [email protected]
            // assign other deps here as per your needs
          }
        }
      }, themeResId = R.style.AppTheme)
    }

}

Where TestApp is declared as such:


    class TestApp : Application()

This might be needed in order to refrain from error, which will happen in your custom application class's onCreate() method, where Dagger tree is being constructed.

After having this setup you can start writing your unit test:

@Test
fun `navigation to login screen is correctly performed`() {
    val navController: NavController = mock()

    val scenario = launchFragment()
    scenario.onFragment {
        Navigation.setViewNavController(it.requireView(), navController)
    }

    onView(withId(R.id.button_login)).perform(click())
    verify(mockNavController).navigate(R.id.action_intro_fragment_to_login_fragment)
}
like image 85
azizbekian Avatar answered Sep 21 '22 07:09

azizbekian