Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Casting a parameter mock object to another object using Mockito

I have a method to test, that begins following:

public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
    AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo;
    contextString = adapter.getItem(info.position);
        /.../
    }

I would like to test it using Mockito, but if I declare menuInfo like this:

@Mock ContextMenuInfo menuInfo

then I cannot compile the following statement:

Mockito.when(menuInfo.position).thenReturn(1);

since it is not valid for ContextMenuInfo object. I cannot declare my object as AdapterView.AdapterContextMenuInfo class, since then I get an error during run time.

I know that in Mockito it is possible that a mock implements multiple interfaces, but the same does not apply to classes. How would it be possible to test the method I shown above?

like image 371
Rebane Lumes Avatar asked Aug 09 '13 15:08

Rebane Lumes


2 Answers

Mockito works by using Java inheritance to replace the implementations of methods on a class. However, it looks like position is a field on AdapterContextMenuInfo, which means that Mockito can't mock it for you.

Luckily, AdapterContextMenuInfo has a public constructor, so you don't have to mock it—you can just create one for the test and pass it into your method.

@Test public void contextMenuShouldWork() {
  ContextMenuInfo info =
      new AdapterView.AdapterContextMenuInfo(view, position, id);
  systemUnderTest.onCreateContextMenu(menu, view, info);

  /* assert success here */
}

If you're ever stuck in this pattern for a class that you can't directly mock or instantiate, consider creating a helper class that you can mock:

class MyHelper {

  /** Used for testing. */
  int getPositionFromContextMenuInfo(ContextMenuInfo info) {
    return ((AdapterContextMenuInfo) info).position;
  }
}

Now you can refactor your View to use that:

public class MyActivity extends Activity {
  /** visible for testing */
  MyHelper helper = new MyHelper();

  public void onCreateContextMenu(
      ContextMenu menu, View v, ContextMenuInfo menuInfo) {
    int position = helper.getPositionFromContextMenuInfo(menuInfo);
    // ...
  }
}

...and then mock the helper in your test.

/** This is only a good idea in a world where you can't instantiate the type. */
@Test public void contextMenuShouldWork() {
  ContextMenuInfo info = getSomeInfoObjectYouCantChange();

  MyHelper mockHelper = Mockito.mock(MyHelper.class);
  when(mockHelper.getPositionFromContextMenu(info)).thenReturn(42);
  systemUnderTest.helper = mockHelper;
  systemUnderTest.onCreateContextMenu(menu, view, info);

  /* assert success here */
}    

There is one other option, involving a refactor:

public class MyActivity extends Activity {
  public void onCreateContextMenu(
      ContextMenu menu, View v, ContextMenuInfo menuInfo) {
    AdapterView.AdapterContextMenuInfo info =
        (AdapterView.AdapterContextMenuInfo) menuInfo;
    onCreateContextMenuImpl(info.position);
  }

  /** visible for testing */
  void onCreateContextMenuImpl(int position) {
    // the bulk of the code goes here
  }
}

@Test public void contextMenuShouldWork() {
  systemUnderTest.onCreateContextMenuImpl(position);

  /* assert success here */
}
like image 187
Jeff Bowman Avatar answered Oct 18 '22 01:10

Jeff Bowman


May be use mockito's extraInterfaces option

        @Mock(extraInterfaces=AdapterView.AdapterContextMenuInfo.class) 
        ContextMenuInfo menuInfo

and then mock it like

Mockito.doReturn(1).when((AdapterView.AdapterContextMenuInfo)menuInfo).position
like image 33
jsphdnl Avatar answered Oct 18 '22 01:10

jsphdnl