Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mock getApplicationContext

Tags:

I have an application that stores app context information. The app context information is shared between activities in MyApp class which extends Application class.

I am writing a unit test for my activity, and I want to check that when user clicks a button in the activity, an application state will change. Something like this:

@Override public void onClick(View pView) {     ((MyApp)getApplicationContext()).setNewState(); }    

The problem is that I don't know how to mock that application context. I am using ActivityUnitTestCase as a test case base. When I call setApplication, it changes the value of mApplication member of Activity class, but not application context. I've tried setActivityContext also, but it seems wrong (it is not app context but activity context) and it fires assert inside startActivity).

So the question is - how to mock getApplicationContext()?

like image 872
lstipakov Avatar asked Apr 16 '11 11:04

lstipakov


1 Answers

Since the method getApplicationContext is inside the class that you're extending it becomes somewhat problematic. There are a couple of problems to consider:

  • You really can't mock a class that is under test, which is one of the many drawbacks with object inheritance (i.e. subclassing).
  • The other problem is that ApplicationContext is a singleton, which makes it all more evil to test since you can't easily mock out a global state that is programmed to be irreplaceable.

What you can do in this situation is to prefer object composition over inheritance. So in order to make your Activity testable you need to split up the logic a little. Lets say that your Activity is called MyActivity. It needs to be composed of a logic component (or class), lets name it MyActivityLogic. Here is a simple class-diagram figure:

MyActivity and MyActivityLogic UML diagram from yUml

To solve the singleton problem, we let the logic be "injected" with an application context, so it can be tested with a mock. We then only need to test that the MyActivity object has put the correct application context into MyActivityLogic. How we basically solve both problems is through another layer of abstraction (paraphrased from Butler Lampson). The new layer we add in this case is the activity logic moved outside of the activity object.

For the sake of your example the classes need to look sort-of like this:

public final class MyActivityLogic {      private MyApp mMyApp;      public MyActivityLogic(MyApp pMyApp) {         mMyApp = pMyApp;     }      public MyApp getMyApp() {         return mMyApp;     }      public void onClick(View pView) {         getMyApp().setNewState();     } }  public final class MyActivity extends Activity {      // The activity logic is in mLogic     private final MyActivityLogic mLogic;      // Logic is created in constructor     public MyActivity() {         super();          mLogic = new MyActivityLogic(             (MyApp) getApplicationContext());     }      // Getter, you could make a setter as well, but I leave     // that as an exercise for you     public MyActivityLogic getMyActivityLogic() {         return mLogic;     }      // The method to be tested     public void onClick(View pView) {         mLogic.onClick(pView);     }      // Surely you have other code here...  } 

It should all look something like this: classes with methods made in yUml

To test MyActivityLogic you will only need a simple jUnit TestCase instead of the ActivityUnitTestCase (since it isn't an Activity), and you can mock your application context using your mocking framework of choice (since handrolling your own mocks is a bit of a drag). Example uses Mockito:

MyActivityLogic mLogic; // The CUT, Component Under Test MyApplication mMyApplication; // Will be mocked  protected void setUp() {     // Create the mock using mockito.       mMyApplication = mock(MyApplication.class);     // "Inject" the mock into the CUT       mLogic = new MyActivityLogic(mMyApplication); }  public void testOnClickShouldSetNewStateOnAppContext() {     // Test composed of the three A's             // ARRANGE: Most stuff is already done in setUp      // ACT: Do the test by calling the logic     mLogic.onClick(null);      // ASSERT: Make sure the application.setNewState is called     verify(mMyApplication).setNewState(); } 

To test the MyActivity you use ActivityUnitTestCase as usual, we only need to make sure that it creates a MyActivityLogic with the correct ApplicationContext. Sketchy test code example that does all this:

// ARRANGE: MyActivity vMyActivity = getActivity(); MyApp expectedAppContext = vMyActivity.getApplicationContext();  // ACT:  // No need to "act" much since MyActivityLogic object is created in the  // constructor of the activity MyActivityLogic vLogic = vMyActivity.getMyActivityLogic();  // ASSERT: Make sure the same ApplicationContext singleton is inside // the MyActivityLogic object MyApp actualAppContext = vLogic.getMyApp(); assertSame(expectedAppContext, actualAppContext); 

Hope it all makes sense to you and helps you out.

like image 172
Spoike Avatar answered Sep 24 '22 06:09

Spoike