Reputation: 62549
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
Upvotes: 5
Views: 13357
Reputation: 656
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.
Upvotes: 0
Reputation: 62549
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'
Upvotes: 1
Reputation: 95704
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.
Upvotes: 3