Skip to content

Commit

Permalink
Mockito.mockStatic can take over from PowerMock now
Browse files Browse the repository at this point in the history
The mockito dependency upgrade is actually a big feature switch, so
the previous usage of PowerMock can be removed and mockito can do all
of our static mocking
  • Loading branch information
mikehardy committed Jul 16, 2020
1 parent f8548a7 commit 9ac3c32
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 122 deletions.
5 changes: 1 addition & 4 deletions AnkiDroid/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -254,10 +254,7 @@ dependencies {
api project(":api")

testImplementation 'org.junit.vintage:junit-vintage-engine:5.6.2'
testImplementation 'org.mockito:mockito-core:3.3.3'
testImplementation 'org.powermock:powermock-core:2.0.7'
testImplementation 'org.powermock:powermock-module-junit4:2.0.7'
testImplementation 'org.powermock:powermock-api-mockito2:2.0.7'
testImplementation 'org.mockito:mockito-inline:3.4.0'
testImplementation 'org.hamcrest:hamcrest-all:1.3'
testImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1'
testImplementation "org.robolectric:robolectric:4.3.1"
Expand Down
105 changes: 54 additions & 51 deletions AnkiDroid/src/test/java/com/ichi2/anki/AnalyticsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,15 @@
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

@PowerMockIgnore("javax.net.ssl.*")
@PrepareForTest({PreferenceManager.class, GoogleAnalytics.class})
@RunWith(PowerMockRunner.class)

import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;

public class AnalyticsTest {

@Mock
Expand All @@ -60,12 +56,10 @@ public class AnalyticsTest {
// This is actually a Mockito Spy of GoogleAnalyticsImpl
private GoogleAnalytics mAnalytics;


@Before
public void setUp() {
PowerMockito.mockStatic(PreferenceManager.class);
PowerMockito.mockStatic(GoogleAnalytics.class);

MockitoAnnotations.initMocks(this);
MockitoAnnotations.openMocks(this);

Mockito.when(mMockResources.getBoolean(R.bool.ga_anonymizeIp))
.thenReturn(true);
Expand All @@ -85,68 +79,77 @@ public void setUp() {

Mockito.when(mMockSharedPreferences.getBoolean(UsageAnalytics.ANALYTICS_OPTIN_KEY, false))
.thenReturn(true);
Mockito.when(PreferenceManager.getDefaultSharedPreferences(ArgumentMatchers.any()))
.thenReturn(mMockSharedPreferences);


Mockito.when(mMockSharedPreferencesEditor.putBoolean(UsageAnalytics.ANALYTICS_OPTIN_KEY, true))
.thenReturn(mMockSharedPreferencesEditor);

Mockito.when(mMockSharedPreferences.edit())
.thenReturn(mMockSharedPreferencesEditor);

Mockito.when(GoogleAnalytics.builder())
.thenReturn(new SpyGoogleAnalyticsBuilder());

mAnalytics = UsageAnalytics.initialize(mMockContext);
}


private static class SpyGoogleAnalyticsBuilder extends GoogleAnalyticsBuilder {
public GoogleAnalytics build() {
GoogleAnalytics analytics = super.build();
return Mockito.spy(analytics);
}
}


@After
public void validate() {
Mockito.validateMockitoUsage();
}


@Test
public void testSendException() {

// no root cause
Exception exception = Mockito.mock(Exception.class);
Mockito.when(exception.getCause()).thenReturn(null);
Throwable cause = UsageAnalytics.getCause(exception);
Mockito.verify(exception).getCause();
Assert.assertEquals(exception, cause);

// a 3-exception chain inside the actual analytics call
Exception childException = Mockito.mock(Exception.class);
Mockito.when(childException.getCause()).thenReturn(null);
Mockito.when(childException.toString()).thenReturn("child exception toString()");
Exception parentException = Mockito.mock(Exception.class);
Mockito.when(parentException.getCause()).thenReturn(childException);
Exception grandparentException = Mockito.mock(Exception.class);
Mockito.when(grandparentException.getCause()).thenReturn(parentException);

// prepare analytics so we can inspect what happens
ExceptionHit spyHit = Mockito.spy(new ExceptionHit());
Mockito.doReturn(spyHit).when(mAnalytics).exception();

try {
UsageAnalytics.sendAnalyticsException(grandparentException, false);
} catch (Exception e) {
// do nothing - this is expected because UsageAnalytics isn't fully initialized
try (
MockedStatic<PreferenceManager> mockPreferenceManager = mockStatic(PreferenceManager.class);
MockedStatic<GoogleAnalytics> mockGoogleAnalytics = mockStatic(GoogleAnalytics.class)) {

when(PreferenceManager.getDefaultSharedPreferences(ArgumentMatchers.any()))
.thenReturn(mMockSharedPreferences);

when(GoogleAnalytics.builder())
.thenReturn(new SpyGoogleAnalyticsBuilder());

mAnalytics = UsageAnalytics.initialize(mMockContext);

// no root cause
Exception exception = Mockito.mock(Exception.class);
Mockito.when(exception.getCause()).thenReturn(null);
Throwable cause = UsageAnalytics.getCause(exception);
Mockito.verify(exception).getCause();
Assert.assertEquals(exception, cause);

// a 3-exception chain inside the actual analytics call
Exception childException = Mockito.mock(Exception.class);
Mockito.when(childException.getCause()).thenReturn(null);
Mockito.when(childException.toString()).thenReturn("child exception toString()");
Exception parentException = Mockito.mock(Exception.class);
Mockito.when(parentException.getCause()).thenReturn(childException);
Exception grandparentException = Mockito.mock(Exception.class);
Mockito.when(grandparentException.getCause()).thenReturn(parentException);

// prepare analytics so we can inspect what happens
ExceptionHit spyHit = Mockito.spy(new ExceptionHit());
Mockito.doReturn(spyHit).when(mAnalytics).exception();

try {
UsageAnalytics.sendAnalyticsException(grandparentException, false);
} catch (Exception e) {
// do nothing - this is expected because UsageAnalytics isn't fully initialized
}
Mockito.verify(grandparentException).getCause();
Mockito.verify(parentException).getCause();
Mockito.verify(childException).getCause();
Mockito.verify(mAnalytics).exception();
Mockito.verify(spyHit).exceptionDescription(ArgumentMatchers.anyString());
Mockito.verify(spyHit).sendAsync();
Assert.assertEquals(spyHit.exceptionDescription(), "child exception toString()");
}
Mockito.verify(grandparentException).getCause();
Mockito.verify(parentException).getCause();
Mockito.verify(childException).getCause();
Mockito.verify(mAnalytics).exception();
Mockito.verify(spyHit).exceptionDescription(ArgumentMatchers.anyString());
Mockito.verify(spyHit).sendAsync();
Assert.assertEquals(spyHit.exceptionDescription(), "child exception toString()");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,35 +18,34 @@

import android.util.Log;

import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.mockito.MockedStatic;

import timber.log.Timber;

import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.atLeast;
import static org.powermock.api.mockito.PowerMockito.verifyStatic;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;

@RunWith(PowerMockRunner.class)
@PrepareForTest(Log.class)
public class ProductionCrashReportingTreeTest {


@Before
public void setUp() {

// setup - simply instrument the class and do same log init as production
PowerMockito.mockStatic(Log.class);
Timber.plant(new AnkiDroidApp.ProductionCrashReportingTree());
}

@After
public void tearDown() {
Timber.uprootAll();
}


/**
* The Production logger ignores verbose and debug logs on purpose
Expand All @@ -55,20 +54,30 @@ public void setUp() {
@Test
public void testProductionDebugVerboseIgnored() {

// set up the platform log so that if anyone calls these 2 methods at all, it throws
Mockito.when(Log.v(anyString(), anyString(), any()))
.thenThrow(new RuntimeException("Verbose logging should have been ignored"));
Mockito.when(Log.d(anyString(), anyString(), any()))
.thenThrow(new RuntimeException("Debug logging should be ignored"));

// now call our wrapper - if it hits the platform logger it will throw
try {
Timber.v("verbose");
Timber.d("debug");
} catch (Exception e) {
Assert.fail("we were unable to log without exception?");
try (MockedStatic<Log> autoClosed = mockStatic(Log.class)) {
// set up the platform log so that if anyone calls these 2 methods at all, it throws
when(Log.v(anyString(), anyString(), any()))
.thenThrow(new RuntimeException("Verbose logging should have been ignored"));
when(Log.d(anyString(), anyString(), any()))
.thenThrow(new RuntimeException("Debug logging should be ignored"));
when(Log.i(anyString(), anyString(), any()))
.thenThrow(new RuntimeException("Info logging should throw!"));

// now call our wrapper - if it hits the platform logger it will throw
try {
Timber.v("verbose");
Timber.d("debug");
} catch (Exception e) {
Assert.fail("we were unable to log without exception?");
}

try {
Timber.i("info");
Assert.fail("we should have gone to Log.i and thrown but did not? Testing mechanism failure.");
} catch (Exception e) {
// this means everything worked, we were counting on an exception
}
}

}


Expand All @@ -82,19 +91,17 @@ public void testProductionDebugVerboseIgnored() {
@SuppressWarnings("PMD.JUnitTestsShoudIncludAssert")
public void testProductionLogTag() {

// setUp() instrumented the static, now exercise it
Timber.i("info level message");
Timber.w("warn level message");
Timber.e("error level message");
try (MockedStatic<Log> autoClosed = mockStatic(Log.class)) {

// verify that info level had the constant tag
verifyStatic(Log.class, atLeast(1));
Log.i(AnkiDroidApp.TAG, "info level message", null);
// Now let's run through our API calls...
Timber.i("info level message");
Timber.w("warn level message");
Timber.e("error level message");

// verify Warn/Error has final part of calling class name to start the message
verifyStatic(Log.class, atLeast(1));
Log.w(AnkiDroidApp.TAG, this.getClass().getSimpleName() + "/ " + "warn level message", null);
verifyStatic(Log.class, atLeast(1));
Log.e(AnkiDroidApp.TAG, this.getClass().getSimpleName() + "/ " + "error level message", null);
// ...and make sure they hit the logger class post-processed correctly
autoClosed.verify(() -> Log.i(AnkiDroidApp.TAG, "info level message", null));
autoClosed.verify(() -> Log.w(AnkiDroidApp.TAG, this.getClass().getSimpleName() + "/ " + "warn level message", null));
autoClosed.verify(() -> Log.e(AnkiDroidApp.TAG, this.getClass().getSimpleName() + "/ " + "error level message", null));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.powermock.modules.junit4.PowerMockRunner;
import org.mockito.MockitoAnnotations;

import static com.ichi2.testutils.AnkiAssert.assertDoesNotThrow;
import static com.ichi2.utils.FunctionalInterfaces.Supplier;
Expand All @@ -16,8 +15,6 @@
import static org.mockito.ArgumentMatchers.eq;

//Unknown issue: @CheckResult should provide warnings on this class when return value is unused, but doesn't.
//TODO: The preference mock is messy
@RunWith(PowerMockRunner.class)
public class PreferenceExtensionsTest {

private static Supplier<String> UNUSED_SUPPLIER = () -> { throw new UnexpectedException();};
Expand All @@ -40,6 +37,7 @@ private String getOrSetString(String key, Supplier<String> supplier) {

@Before
public void setUp() {
MockitoAnnotations.openMocks(this);
Mockito.when(mMockReferences.contains(VALID_KEY)).thenReturn(true);
Mockito.when(mMockReferences.getString(eq(VALID_KEY), anyString())).thenReturn(VALID_RESULT);
Mockito.when(mMockReferences.edit()).thenReturn(mockEditor);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@
import com.wildplot.android.rendering.graphics.wrapper.GraphicsWrap;
import com.wildplot.android.rendering.graphics.wrapper.RectangleWrap;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.modules.junit4.PowerMockRunnerDelegate;

import java.util.ArrayList;
import java.util.Arrays;
Expand All @@ -30,14 +30,10 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.floatThat;
import static org.mockito.Mockito.inOrder;
import static org.powermock.api.mockito.PowerMockito.mock;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
import static org.powermock.api.mockito.PowerMockito.when;

@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(Parameterized.class)
@PrepareForTest({RectangleWrap.class, GraphicsWrap.class, ColorWrap.class, PlotSheet.class,
Color.class})
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@RunWith(Parameterized.class)
@SuppressWarnings("WeakerAccess")
public class PieChartParameterizedTest {
private static final double PRECISION = 2 * 1E-3F;
Expand All @@ -59,10 +55,12 @@ public class PieChartParameterizedTest {

PieChart pieChart;

private MockedStatic<Color> colorMockedStatic;

@Before
public void setUp() {
mockStatic(android.graphics.Color.class);
MockitoAnnotations.initMocks(this);
colorMockedStatic = Mockito.mockStatic(Color.class);
MockitoAnnotations.openMocks(this);
when(Color.argb(anyInt(), anyInt(), anyInt(), anyInt())).thenReturn(0);
when(plot.getFrameThickness()).thenReturn(new float[]{0, 0, 0, 0});

Expand All @@ -76,6 +74,11 @@ public void setUp() {
pieChart = new PieChart(plot, values, colors);
}

@After
public void tearDown() {
colorMockedStatic.close();
}

@Test
public void testPaintDrawsAllArcs() {
pieChart.paint(graphics);
Expand Down

0 comments on commit 9ac3c32

Please sign in to comment.