I am trying to use PowerMock to prototype a class using a static method, but in particular I want to do this in an Android Instrumentation test. To make it clear, I want to run the test on a real Android device or emulator. I am using Android Studio (1.5.1) and Gradle (1.5.0). To avoid the βred herringsβ, I created a very simple and rather rude Hello World application. This application simply shows 2 pieces of text, one of which is obtained by the static method and one of the non-static method. I wrote instrumental tests for both of these text provider classes. You can see this application here:
https://github.com/Kai2k/PowerMockAndroidTest.git
When I try to run instrumental tests, I get an error message that many people seem to be trying to get:
com.android.build.api.transform.TransformException: com.android.builder.packaging.DuplicateFileException: Duplicate files copied in APK mockito-extensions/org.mockito.plugins.MockMaker File1: /Users/kaiarmer/.gradle/caches/modules-2/files-2.1/com.crittercism.dexmaker/dexmaker-mockito/1.4/70892a94894462c1b35df3c8a77d21b7e843550b/dexmaker-mockito-1.4.jar File2: /Users/kaiarmer/.gradle/caches/modules-2/files-2.1/org.powermock/powermock-api-mockito/1.6.4/fe12509b7e9e49d25131f4155145748a31e42e40/powermock-api-mockito-1.6.4.jar
My dependencies on my build.gradle application file look like this:
dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') testCompile 'junit:junit:4.12' testCompile 'org.powermock:powermock-api-mockito:1.6.4' testCompile 'org.powermock:powermock-module-junit4-rule-agent:1.6.4' testCompile 'org.powermock:powermock-module-junit4-rule:1.6.4' testCompile 'org.powermock:powermock-module-junit4:1.6.4' androidTestCompile 'org.mockito:mockito-core:1.10.19' androidTestCompile 'org.powermock:powermock-api-mockito:1.6.4' androidTestCompile 'com.android.support:support-annotations:23.1.1' androidTestCompile 'com.android.support.test:runner:0.4.1' androidTestCompile 'com.android.support.test:rules:0.4.1' androidTestCompile 'org.hamcrest:hamcrest-library:1.3' androidTestCompile 'com.crittercism.dexmaker:dexmaker:1.4' androidTestCompile 'com.crittercism.dexmaker:dexmaker-mockito:1.4' compile 'com.android.support:appcompat-v7:23.1.1' compile 'com.android.support:design:23.1.1' }
You will notice some dependencies of 'testCompile on power mock and related libraries (in addition to androidTestCompile). This is because for the belt and braces, I wanted to see if I could also perform the Junit test (not the instrumental one) (which uses a power layout). I was able to achieve this, but not instrument testing. Here is my (terrible) code:
Primary activity:
package com.example.android.powermocktest; import android.os.Bundle; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.Snackbar; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.View; import android.view.Menu; import android.view.MenuItem; import android.widget.TextView; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) .setAction("Action", null).show(); } }); TextView textView1 = (TextView)findViewById(R.id.textView1); textView1.setText(GetStaticValue.getTheStaticValue()); TextView textView2 = (TextView)findViewById(R.id.textView2); textView2.setText(new GetNonStaticValue().getNonStaticString()); } @Override public boolean onCreateOptionsMenu(Menu menu) {
Layout Files:
<?xml version="1.0" encoding="utf-8"?> <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" tools:context="com.example.android.powermocktest.MainActivity"> <android.support.design.widget.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="@style/AppTheme.AppBarOverlay"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" app:popupTheme="@style/AppTheme.PopupOverlay" /> </android.support.design.widget.AppBarLayout> <include layout="@layout/content_main" /> <android.support.design.widget.FloatingActionButton android:id="@+id/fab" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|end" android:layout_margin="@dimen/fab_margin" android:src="@android:drawable/ic_dialog_email" /> </android.support.design.widget.CoordinatorLayout> <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:context="com.example.android.powermocktest.MainActivity" tools:showIn="@layout/activity_main"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/textView1" android:text="Hello World!" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/textView1" android:id="@+id/textView2" android:text="Hello World again!" /> </RelativeLayout>
And classes for getting static and non-static values:
package com.example.android.powermocktest; public class GetStaticValue { private final static String THE_VALUE = "Hi, I'm static"; public static String getTheStaticValue(){ return THE_VALUE; } } package com.example.android.powermocktest; public class GetNonStaticValue { public String getNonStaticString(){ return "Hi, I'm non-static"; } }
And finally, test classes. Instrument tests first:
package com.example.android.powermocktest; import android.support.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; import static org.powermock.api.mockito.PowerMockito.when; @RunWith(AndroidJUnit4.class) @PrepareForTest(GetStaticValue.class) public class GetStaticValueTest { @Test public void getStaticValueReturnsValue(){ PowerMockito.mockStatic(GetStaticValue.class); when(GetStaticValue.getTheStaticValue()).thenReturn("Hi, I'm static"); assertThat(GetStaticValue.getTheStaticValue(), equalTo("Hi, I'm static")); } } package com.example.android.powermocktest; import android.support.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; import static org.mockito.Mockito.when; @RunWith(AndroidJUnit4.class) public class GetNonStaticValueTest { @Test public void getNonStaticValueReturnsNonStaticValue(){ GetNonStaticValue getNonStaticValue = Mockito.mock(GetNonStaticValue.class); when(getNonStaticValue.getNonStaticString()).thenReturn("Hi, I'm non-static"); assertThat(getNonStaticValue.getNonStaticString(), equalTo("Hi, I'm non-static")); } }
And now jUnit tests (which work):
package com.example.android.powermocktest; import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; import static org.powermock.api.mockito.PowerMockito.when; @RunWith(PowerMockRunner.class) @PrepareForTest(GetStaticValue.class) public class GetStaticValueTest { @Test public void getStaticValueReturnsValue() { PowerMockito.mockStatic(GetStaticValue.class); when(GetStaticValue.getTheStaticValue()).thenReturn("Hi, I'm static"); assertThat(GetStaticValue.getTheStaticValue(), equalTo("Hi, I'm static")); } }
What I have tried so far:
I pondered this problem a bit and found one solution that I have not tried is to crack the jars of the Power Mock library and remove the duplicate class. Since (at work) we use gradle and do not work with local banks, I would prefer not to.
How to get Powermock to work with Dexmaker
I heard people suggest using an exception in the Android section of the Gradle build file. This is not suitable, since for testing the toolkit it is created (Android Studio / Gradle) in the form of apk and launched on the device. Thus, to run tests, jars with simulated power are needed.
Android Studio: gradle dependency error
I tried to remove some of the dependencies that you see in my build file above. For example, I tried to remove the dependency "org.powermock: powermock-api-mockito", but, for example, you must use "mockStatic".
It seems like someone was successful when using Maven, telling Maven to exclude duplicates:
https://groups.google.com/forum/#!searchin/powermock/android/powermock/ndZ2ZliYGCY/Eh226605u2cJ
PowerMock + Mockito + Maven in Android app with Dex Loader error
I tried to figure out if there is a way to tell Gradle to ignore duplicate classes in dependency banks, but so far I have not succeeded.
I feel that this is worth continuing, because, firstly, the power model can be very useful when used sparingly, and secondly, I canβt believe that this problem has not been resolved (it has probably been encountered before!) .
In conclusion, I noticed that Google itself, by its very nature, seems to suggest that ridicule is only for unit tests, not instrumental tests
http://developer.android.com/training/testing/start/index.html
I would really appreciate any help. Thank you