Question

I am trying to test a Fragment I've created in Android. I have complete control of the code, so I can change it as I see fit. The issue is that I'm not sure what design pattern I'm missing to make it reasonable.

I am looking for a way to mock objects in Android that are not passed as parameters. This question suggests that anything you might want to mock should be written to be passed as a parameter.

This makes sense for some situations, but I can't figure out how to get it working on Android, where some of this isn't possible. With a Fragment, for example, you're forced to let much of the heavy lifting be done in callback methods. How can I get my mocked objects into the Fragment?

For example, in this ListFragment I need to retrieve an array of things to display to the user. The things I'm displaying need to be retrieved dynamically and added to a custom adapter. It currently looks as follows:

public class MyFragment extends ListFragment {

  private List<ListItem> mList;

  void setListValues(List<ListItem> values) {
    this.mList = values;
  }

  List<ListItem> getListValues() {
    return this.mList;
  }

  @Override
  public void onCreateView(LayoutInflater i, ViewGroup vg, Bundle b) {
    // blah blah blah
  }

  @Override
  public void onViewCreated(View view, Bundle savedInstanceState) {
    this.setListValues(ListFactory.getListOfDynamicValues());
    CustomAdapter adapter = new CustomAdapter(
        getActivity(),
        R.layout.row_layout,
        this.getListValues());
    this.setListAdapter(adapter);
  }

}

I'm trying to do this using Mockito and Robolectric.

This is the beginning of my robolectric test case:

public class MyFragmentTest {

  private MyFragment fragment;

  @Before
  public void setup() {
    ListItem item1 = mock(ListItem.class);
    ListItem item2 = mock(ListItem.class);
    when(item1.getValue()).thenReturn("known value 1");
    when(item2.getValue()).thenReturn("known value 2");
    List<ListItem> mockList = new ArrayList<ListItem>();
    mockList.add(item1);
    mockList.add(item2);
    MyFragment real = new MyFragment();
    this.fragment = spy(real);
    when(this.fragment.getValueList()).thenReturn(mockList);
    startFragment();
  }

}

This feels so very wrong. This section from the mockito api points out that you shouldn't have to do partial mocks like this very frequently unless you're dealing with legacy code.

Further, I'm not actually able to mock out the CustomAdapter class using this approach.

What is the right way to do this sort of thing? Am I structuring things incorrectly in my Fragment classes? I suppose I might be able to add a bunch of package-private setters, but this still doesn't feel right.

Can someone shed some light on this? I'm happy to do rewrites, I just want to know some good patterns for dealing with the state in my Fragments and how I can make them testable.

Was it helpful?

Solution

I ended up creating my own solution to this. My approach was to add another level of indirection to each my calls that create or set an object.

First, let me point out that I couldn't actually get Mockito to work reliably with Fragment or Activity objects. It was somewhat hit or miss, but especially with trying to create Mockito Spy objects, some lifecycle methods appeared to not be called. I think this is related to gotcha number 2 shown here. Perhaps this is due to the ways that Android uses reflection to recreate and instantiate activities and fragments? Note that I was NOT incorrectly holding onto the reference, as it warns of, but interacting only with the Spy, as indicated.

So, I wasn't able to mock Android objects that required lifecycle methods be invoked by the framework.

My solution was to create to more types of methods in my Activity and Fragment methods. These methods are:

  • getters (getX()) that return the field named X.
  • retrievers (retrieveX()) that do some sort of work to get an object.
  • creators (createMyFragment()) that create objects by calling new. Similar to the retrievers.

Getters have whatever visibility you need. Mine are usually public or private.

Retrievers and creators are package private or protected, allowing you to override them in your test packages but not making them generally available. The idea behind these methods is that you can subclass your regular objects with stub objects and inject in known values during testing. You could also just mock out those methods if Mockito mocks/spies are working for you.

Taken in toto, the test would look something like the following.

Here is the fragment from my original question, modified to use the above approach. This is in the normal project:

package org.myexample.fragments

// imports

public class MyFragment extends ListFragment {

  private List<ListItem> mList;

  void setListValues(List<ListItem> values) {
    this.mList = values;
  }

  List<ListItem> getListValues() {
    return this.mList;
  }

  @Override
  public void onCreateView(LayoutInflater i, ViewGroup vg, Bundle b) {
    // blah blah blah
  }

  @Override
  public void onViewCreated(View view, Bundle savedInstanceState) {
    this.setListValues(this.retrieveListItems());
    CustomAdapter adapter = this.createCustomAdapter();
    this.setListAdapter(adapter);
  }

  List<ListItem> retrieveListItems() {
    List<Item> result = ListFactory.getListOfDynamicValues();
    return result;
  }

  CustomAdapter createCustomAdapter() {
    CustomAdapter result = new CustomAdapter(
        this.getActivity();
        R.layout.row_layout,
        this.getListValues());
    return result;
  }

}

When I test this object, I want to be able to control what gets passed around. My first thought was to use a Spy, replacing the return values of retrieveListItems() and createCustomAdapter() with my known values. However, like I said above, I wasn't able to get Mockito spies to behave when working with fragments. (Especially ListFragments--I had mixed success with other types, but don't trust it.) So, we are going to subclass this object. In the test project, I have the following. Note that your method visibility in your real class must allow subclasses to override, so it needs to be package private and in the same package or protected. Note that I am overriding the retriever and creator, returning instead static variables that my tests will set.

package org.myexample.fragments

// imports

public class MyFragmentStub extends MyFragment {

  public static List<ListItem> LIST = null;
  public static CustomAdapter ADAPTER = null;


  /**
   * Resets the state for the stub object. This should be called
   * in the teardown methods of your test classes using this object.
   */
  public static void resetState() {
    LIST = null;
    ADAPTER = null;
  }

  @Override
  List<ListItem> retrieveListItems() {
    return LIST_ITEMS;
  }

  @Override
  CustomAdapter createCustomAdapter() {
    return CUSTOM_ADAPTER;
  }

}

In the same package in my test project I have the actual test of the fragment. Note that while I'm using Robolectric, this should work with whatever test framework you're using. The @Before annotation becomes less useful, as you need to update your static state for individual tests.

package org.myexample.fragments

// imports

@RunWith(RobolectricTestRunner.class)
public class MyFragmentTest  {

  public MyFragment fragment;
  public Activity activity;

  @After
  public void after() {
    // Very important to reset the state of the object under test,
    // as otherwise your tests will affect each other.
    MyFragmentStub.resetState();
  }

  private void setupState(List<ListItem> testList, CustomAdapter adapter) {
    // Set the state you want the fragment to use.
    MyFragmentStub.LIST = testList;
    MyFragmentStub.ADAPTER = adapter;
    MyFragmentStub stub = new MyFragmentStub();
    // Start and attach the fragment using Robolectric.
    // This method doesn't call visible() on the activity, though so
    // you'll have to do that yourself.
    FragmentTestUtil.startFragment(stub);
    Robolectric.ActivityController.of(stub.getActivity()).visible();
    this.fragment = stub;
    this.activity = stub.getActivity();

  }

  @Test
  public void dummyTestWithKnownValues() {
    // This is a test that does nothing other than show you how to use
    // the stub.
    // Create whatever known values you want to test with.
    List<ListItem> list = new ArrayList<ListItem>();
    CustomAdapter adapter = mock(CustomAdapter.class);
    this.setupState(list, adapter);
    // android fest assertions
    assertThat(this.fragment).isNotNull();
  }

}

This is definitely more verbose than using a mocking framework. However, it works even with Android's life cycle. If I'm testing an Activity, I'll also often include a static boolean BUILD_FRAGMENTS variable. If true, I'll go call through to super in the appropriate methods or return a known fragment as appropriate. In this way I'm able to inject my test objects and play nice with the Android life cycle.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top