Confused how to use Mockito for an android test

11,772

Solution 1

That's how mockito works, but the problem is: is your listViewButton using your mockedApp? Seems not, because you're creating mockedApp at the test method and never setting it anywhere. Mockito will not mock the method calls of all instances of Application, only from what you declared as a mock.

I personally don't know how android works with the Application class, but you will have to set it somewhere so listView use your mockedApp instead of what it receives normally.

EDIT After the updated question, you can transform your getApplication in a protected method, spy you listViewButton and make it return your mockedApp. That smells a little bad, but it's one way if you can not set your application mocked object to listViewButton.

EDIT2

Example of using spy in your test using BDDMockito for readability :)

public HomeActivity {
    ...
    protected MyApplication getApplication() {
       // real code
    }
    ...
}

public void TestHomeActivity {
   private HomeActivity homeActivity;

   @Before
   public void setUp() {
       this.homeActivity = spy(new HomeActivity());
   }

   @Test
   public void buttonShouldOpenListIfConnected() {
       // given
       FlexApplication mockedApp = Mockito.mock(MyApplication.class);
       Mockito.when(mockedApp.isDeviceConnected()).thenReturn(true);
       // IMPORTANT PART
       given(homeActivity.getApplication()).willReturn(mockedApp);
       ...
   }
}

After that, your test should work as expected. But I reinforce: Use spy only if you can't inject your mockedApp inside HomeActivity.

Solution 2

Your mocked version isn't being called.

See that call, getApplication()? (below). That's returning a real copy of your MyApplication class, not your mocked version. You'd need to intercept the getApplication() call and pass in your mocked Application object.

HomeActivity.java:

//this gets called when the button to open the list is clicked.
public void openListActivity(View button) { 
    MyApplication myApplication = (MyApplication) getApplication(); // returns the real thing
    if (myApplication.isDeviceConnected() {
        startActivity(new intent(this, ListActivity.class));
   }
}

I'm not sure this is possible with Mockito. Have you tried customizing the ShadowActivity#getApplication() method?

Share:
11,772
Matt Wolfe
Author by

Matt Wolfe

Updated on July 20, 2022

Comments

  • Matt Wolfe
    Matt Wolfe almost 2 years

    I'm trying to write a unit test for my android app but having trouble doing what I want with mockito. This is being used in conjunction with Robolectric which I have working just fine and have demonstrated that unit tests work.

    I want to test whether or not a button will open a new activity depending on whether there is some bluetooth device connected. Obviously, there is no device connected with bluetooth in my test, however I want to pretend as though there is. The state of the bluetooth connection is stored in my Application class. There is no publicly accessible method to change this value.

    So basically the logic in the app is like this:

    HomeActivity.java:

    //this gets called when the button to open the list is clicked.
    public void openListActivity(View button) { 
      MyApplication myApplication = (MyApplication) getApplication();
      if (myApplication.isDeviceConnected() {
          startActivity(new intent(this, ListActivity.class));
       }
    }
    

    So to test this I did the following:

    TestHomeActivity.java:

    @Test
    public void buttonShouldOpenListIfConnected() {
        FlexApplication mockedApp = Mockito.mock(MyApplication.class);
        Mockito.when(mockedApp.isDeviceConnected()).thenReturn(true);
        //listViewButton was setup in @Before
        listViewButton.performClick();
        ShadowActivity shadowActivity = Robolectric.shadowOf(activity);
    
        Intent intent = shadowActivity.getNextStartedActivity();
        assertNotNull(intent); //this fails because no new activity was opened. I debugged this and found that isDeviceConnected returned false.
        ShadowIntent shadowIntent = Robolectric.shadowOf(intent);
        assertThat(shadowIntent.getComponent().getClassName(), equalTo(ListActivity.class.getName()));
    }
    

    So my unit test fails because the call (in the activity) to isDeviceConnected returns false even though I thought I told it to return true with the mock framework. I want my test to have this method return true though. Isn't this what mockito does or am I totally mistaken on how to use mockito?

  • Matt Wolfe
    Matt Wolfe over 11 years
    I figured this was the problem but wasn't sure how to inject the mocked application.. No I haven't tried that yet, thanks for the tip.
  • Matt Wolfe
    Matt Wolfe over 11 years
    I tried this but I get an exception: ava.lang.RuntimeException: java.lang.reflect.InvocationTargetException which happens when showDialog is called from within onCreate of HomeActivity. Didn't throw that exception before though.
  • Matt Wolfe
    Matt Wolfe over 11 years
    if spy objects are a code smell, what would be a better solution? Should I try and Inject Application? Add a setter for the application? Move the logic that handles connectivity state to another class? I'm not currently using a dependency injection framework but perhaps I could. I also don't really like the though of writing methods used just for test but perhaps that is a necessary evil.
  • Eugen Martynov
    Eugen Martynov over 11 years
    Use setter dependency injection - it's easy (we are using RoboGuice). So basically have field application which is by default real application but have some protected setter to inject your application instead
  • Caesar Ralf
    Caesar Ralf over 11 years
    You could inject the application. If by setter or constructor, you have to decide. I prefer by constructor. If you set getapplication as protected, annotated it with guavas @VisibleForTesting. It's good do demarcate