问题
I”m trying to use PowerMock to mock a class with a static method, but specifically I wish to do this within an Android Instrumentation test. To be clear I wish to run the test on a real Android device or an emulator. I am using Android Studio (1.5.1) and Gradle (1.5.0). To avoid any red herrings I have created a really basic and rather crude ‘hello world’ app. This app simply shows 2 pieces of text, one retrieved from a static method and one from a non-static method. I have written instrumentation tests for both of these ‘text provider’ classes. You can see this app here:
https://github.com/Kai2k/PowerMockAndroidTest.git
When attempting to run the instrumentation tests I get the error which it appears many people trying to achieve this 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 app’s build.gradle 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 ‘testCompile’ dependencies on power mock and related libraries (in addition to androidTestCompile). This is because for belt and braces I wanted to see if I could get a junit (non-instrumentation) test (which uses power mock) working also. I was able to achieve this, but not the instrumentation test. Here is my (horrible) code:
Main 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) {
// 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);
}
}
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 the classes to get the 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 the test classes. First the instrumentation tests:
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 the 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’ve tried so far:
I’ve done some reading around this issue and one solution I’ve found, which I have not tried is to manually crack open the power mock library jars and remove the offending duplicate class. As (at work) we use gradle and don’t work with local jars I’d prefer not to do this.
How to get Powermock to work with Dexmaker
I’ve heard people suggest using ‘exclude’ within the Android section of the gradle build file. This is not suitable as for the instrumentation test to run it is built (by Android Studio / Gradle) as an apk and launched on the device. As such the power mock jars are needed to run the tests.
android studio: gradle dependency error
I’ve tried removing some of the dependencies which you see listed in my build file above. For example I’ve tried removing the ‘org.powermock:powermock-api-mockito’ dependency, but it is needed to use ‘mockStatic’ for example.
It seems that someone was successful when using Maven, by telling Maven to exclude duplicates:
https://groups.google.com/forum/#!searchin/powermock/android/powermock/ndZ2ZliYGCY/Eh226605u2cJ
PowerMock + Mockito + Maven on Android app showing Dex loader error
I’ve tried to see if there is a way of telling Gradle to ignore duplicate classes within dependency jars but so far I have been unsuccessful.
I feel this is worth pursuing as firstly, power mock can be very useful when used sparingly and second, I cannot believe that this issues has not been solved (it’s certainly been encountered before!).
As a final point, I did notice that Google themselves seem to inherently suggest that mocking is only for unit, not instrumentation tests:
http://developer.android.com/training/testing/start/index.html
Any help anyone can offer would be greatly appreciated. Thanks.
回答1:
I was able to workaround this issue with the following. If you see any error message that says Duplicate files copied in APK [filename], add that [filename] to be excluded in the packagingOptions.
android {
packagingOptions {
exclude 'mockito-extensions/org.mockito.plugins.MockMaker'
}
}
回答2:
Do you have this into your project/module gradle?
packagingOptions {
exclude 'fileNameYouWantToExclude'
}
In this way Androd will put just one file if duplicates are found
回答3:
The only correct answer is PowerMockito does not support the Davik VM Android uses, it is meant for a standard JVM. So you can't use it with instrumented tests, only unit tests.
来源:https://stackoverflow.com/questions/35468896/using-powermock-and-mockito-in-an-android-instrumentation-test-error-duplica