5

I have a simple android class of constants like this:

public class Consts extends BaseConstants {

    public static final String SCHEME = "http";

    // Tag used to cancel the request
    public static final String TAG_JSON = "json_obj_req";
}

there is nothing else in it so it should be simple to mock. I am calling this in my testcase:

Mockito.spy(Consts.class); ...which is failing. Below is the testcase file:

public class ApplicationTest extends ActivityInstrumentationTestCase2<MainActivity> {

  MainActivity mActivity;

  public ApplicationTest() {
      super(MainActivity.class);
  }

  @Override
  protected void setUp() throws Exception {
      super.setUp();

      setActivityInitialTouchMode(false);

      mActivity = getActivity();
  }

  public void testUrlValid() {
      Mockito.spy(Consts.class);
  }

}

and here is the logcat output from the test case:

Running tests
Test running started
org.mockito.exceptions.base.MockitoException:
Cannot mock/spy class java.lang.Class
Mockito cannot mock/spy following:
- final classes
- anonymous classes
- primitive types

-------UPDATE:

I want to spy on my mainActivity class but i get the same Mockito exception: heres the class im testing:

import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;


public class MainActivity extends ListPageActivity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      if (savedInstanceState == null) {
          getFragmentManager().beginTransaction()
                .add(R.id.container, new SummaryFragment(),"SummaryFragment")
                .commit();
      }

      loadBrandSound();

     if (!isNetworkAvailable())
          showToast(getString(R.string.no_network));
  }

  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
      // Inflate the menu; this adds items to the action bar if it is   present.
      getMenuInflater().inflate(R.menu.menu_main, menu);
      return true;
  }

  @Override
  public boolean onOptionsItemSelected(MenuItem item) {
      // Handle action bar item clicks here. The action bar will
      // automatically handle clicks on the Home/Up button, so long
      // as you specify a parent activity in AndroidManifest.xml.
      int id = item.getItemId();

      //noinspection SimplifiableIfStatement
      if (id == R.id.action_settings) {
          return true;
      }

      return super.onOptionsItemSelected(item);
  }
}

and here is my simple test case:

import android.test.ActivityInstrumentationTestCase2;
import android.widget.Button;
import android.widget.EditText;

import org.mockito.Mockito;

public class ApplicationTest extends ActivityInstrumentationTestCase2<MainActivity> {

    MainActivity mActivity;
    private Button goBtn;
    private EditText et_query;
    private RecyclerListAdapter mAdapter;

    public ApplicationTest() {
        super(MainActivity.class);
    }

    @Override
    protected void setUp() throws Exception {
        super.setUp();

        setActivityInitialTouchMode(false);

        mActivity = getActivity();

        goBtn=(Button)mActivity.findViewById(
                R.id.btn_go);
        et_query = (EditText)mActivity.findViewById(R.id.et_query);
    }

    @Override
    protected void tearDown() throws Exception {
        super.tearDown();
    }

    public void testPreconditions() {
        assertTrue(mActivity.isNetworkAvailable());
        isLayoutValid();
    }

    public void isLayoutValid(){
        assertNotNull(goBtn);
    }

    public void testSomething(){
        Mockito.spy(MainActivity.class);
    }

}

why is it failing on the spy line ? here is the log:

    Running tests
Test running started
org.mockito.exceptions.base.MockitoException:
Cannot mock/spy class java.lang.Class
Mockito cannot mock/spy following:
- final classes
- anonymous classes
- primitive types
at mypackage.ApplicationTest.testSomething(ApplicationTest.java:65)
at java.lang.reflect.Method.invokeNative(Native Method)
at android.test.InstrumentationTestCase.runMethod(InstrumentationTestCase.java:214)
at android.test.InstrumentationTestCase.runTest(InstrumentationTestCase.java:199)
at android.test.ActivityInstrumentationTestCase2.runTest(ActivityInstrumentationTestCase2.java:192)
at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:191)
at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:176)
at android.test.InstrumentationTestRunner.onStart(InstrumentationTestRunner.java:554)
at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1729)

Finish
Andrew Panasiuk
  • 626
  • 1
  • 8
  • 17
j2emanue
  • 60,549
  • 65
  • 286
  • 456

3 Answers3

3

Unlike Mockito.mock(Class<T> clazz), Mockito.spy(T instance) takes an instance. Both, however, return an instance. You're looking to replace static fields, not instance methods.

Mockito cannot mock static members or final classes, because it works predominantly by creating anonymous proxy implementations or subclasses. Subclasses can't override static methods or extend final classes, so the Java compiler takes a shortcut that skips any Mockito-provided implementations. (This is especially true of static final constants, which are inlined at compile time.)

Though PowerMockito can rewrite system-under-test code to mock final and static method calls, it doesn't help with fields. An additional level of indirection may be a better solution.

public class MyApplication extends Application {
  public boolean isUrlValid(String url) {
    return isUrlValid(url, Consts.SCHEME, Consts.TAG_JSON);
  }

  static boolean isUrlValid(String url, String scheme, String jsonTag) {
    // ...
  }
}

public class MyApplicationTest {
  @Test public void urlValidShouldWork() {
    assertTrue(MyApplication.isUrlValid(VALID_URL, "fakescheme", "faketag");
  }
}

As an alternative, make Consts operate in terms of accessor methods (getScheme), not fields (scheme). Tools like ProGuard can usually inline simple method calls, so it shouldn't be any slower or more verbose in production, and you'll have more opportunity to substitute implementations in tests. For more techniques to insert/inject mocked implementations in tests, see this SO question.

Community
  • 1
  • 1
Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251
  • so lets imagine i had a class that only had 1 static memory variable - am i to understand that i cant spy on this class because of one static member ? – j2emanue Dec 23 '14 at 15:32
  • You can spy on any instance of a non-final class, no matter how many static members it has, but the spying behavior only applies to non-final method calls on the instance `spy` returns. Thus, it's not useful to spy on classes that only contain static constants and methods. (Static classes often are final and have private constructors, and for _those_ classes it _would_ be impossible to mock or spy with Mockito.) – Jeff Bowman Dec 23 '14 at 15:40
  • can you take a look at my updated answer. Really all i did was swap out the constants file for an activity and its still failing with the same error. – j2emanue Dec 23 '14 at 19:15
  • 1
    You're passing in a _class_, not an _instance_. Compare "Mockito.spy(MainActivity.class)" (bad) with "Mockito.spy(new MainActivity())" (good). You could also use `mock` instead, as in "Mockito.mock(MainActivity.class)", but note that with both mocks and spies you won't be able to mock many of your [Activity's `final` methods](http://developer.android.com/reference/android/app/Activity.html), which is why tools like [Robolectric](http://robolectric.org/) exist. – Jeff Bowman Dec 23 '14 at 19:35
1

For any one else having an issue specifically in Android ...i was not including the dex maker jar so that mockito can run with dex files the right way. Add this to your dependencies in gradle:

    androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.0'
androidTestCompile 'com.google.dexmaker:dexmaker:1.0'
j2emanue
  • 60,549
  • 65
  • 286
  • 456
0

There are few points in the question.

Firstly, what's happening when we call Mockito.spy(Consts.class): Mockito tries to proxy an object that you pass into spy method. In this case it is an instance of java.lang.Class that is returned by Consts.class statement. But Mockito cannot proxy an instance of final class which java.lang.Class is. See its code public final class Class

Then, for mocking/spying final classes or static methods you can use PowerMock tool. But it's not suitable for instrumentation tests. See this answer.

So, we should somehow redesign our code. The first option is to redesign the code we are testing. The second - is to use Robolectric and change our test to unit one.

Community
  • 1
  • 1
Andrew Panasiuk
  • 626
  • 1
  • 8
  • 17