Support handling multiple static mocks.

This CL introduces a "StaticMockFixture" interface that allows
setting up the classes to be mocked in a StaticMockitoSessionBuilder
and separately setting up the default behaviour of the so-mocked classes,
as well as tearing them down.

It also introduces a StaticMockFixtureRule that takes some number of
StaticMockFixture instances or suppliers thereof, and manages them all
within the same StaticMockitoSession and its builder.

This in turn allows us to use such a StaticMockFixtureRule to orchestrate
the automatic set-up and tear-down of different sets of static mocks, all
within the same StatickMockitoSession, since juggling multiple
StaticMockitoSessions turns out to be tricky and full of pitfalls.

Finally this change refactors TestableDeviceConfig to become a
StaticMockFixture which can then be used together with other static
mocks, and to separately offer a TestableDeviceConfigRule that can be
used when no other static mocks are required to manage the
TestableDeviceConfig on its own.

Change-Id: Icd88736390699e8956cd3d6923636afc0e015440
Test: included in CL.
Bug: 129937393
This commit is contained in:
Christian Brunschen
2019-06-04 14:38:06 +01:00
parent 0e2fd2605a
commit 58bfeec99c
8 changed files with 760 additions and 39 deletions

View File

@@ -62,7 +62,8 @@ public final class AppCompactorTest {
private CountDownLatch mCountDown;
@Rule
public TestableDeviceConfig mDeviceConfig = new TestableDeviceConfig();
public TestableDeviceConfig.TestableDeviceConfigRule
mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule();
@Before
public void setUp() {

View File

@@ -0,0 +1,163 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.testables;
import static com.google.common.truth.Truth.assertThat;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.List;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class MultipleStaticMocksTest {
@Rule
public StaticMockFixtureRule mStaticMockFixtureRule =
new StaticMockFixtureRule(AB::new, CD::new);
private List<String> mCollected;
@Test
public void testMultipleStaticMocks() throws Exception {
mCollected = new ArrayList<>();
int n = 0;
A.a();
n = verifyCollected(n, "A.a");
D.b();
n = verifyCollected(n, "D.b");
C.b();
n = verifyCollected(n, "C.b");
B.a();
n = verifyCollected(n, "B.a");
}
private int verifyCollected(int n, String... last) {
assertThat(mCollected).hasSize(n + last.length);
assertThat(mCollected.subList(n, mCollected.size()))
.containsExactlyElementsIn(last).inOrder();
return n + last.length;
}
private static class A {
/* package */ static void a() {}
/* package */ static void b() {}
}
private static class B {
/* package */ static void a() {}
/* package */ static void b() {}
}
private static class C {
/* package */ static void a() {}
/* package */ static void b() {}
}
private static class D {
/* package */ static void a() {}
/* package */ static void b() {}
}
/**
* AB StaticMockFixture class that handles two mocked classes, {@link A} and {@link B}.
*/
private class AB implements StaticMockFixture {
@Override
public StaticMockitoSessionBuilder setUpMockedClasses(
StaticMockitoSessionBuilder sessionBuilder) {
sessionBuilder.spyStatic(A.class);
sessionBuilder.spyStatic(B.class);
return sessionBuilder;
}
@Override
public void setUpMockBehaviors() {
ExtendedMockito.doAnswer(invocation -> {
mCollected.add("A.a");
return null;
}).when(A::a);
ExtendedMockito.doAnswer(invocation -> {
mCollected.add("A.b");
return null;
}).when(A::b);
ExtendedMockito.doAnswer(invocation -> {
mCollected.add("B.a");
return null;
}).when(B::a);
ExtendedMockito.doAnswer(invocation -> {
mCollected.add("B.b");
return null;
}).when(B::b);
}
@Override
public void tearDown() {
}
}
/**
* AB StaticMockFixture class that handles two mocked classes, {@link C} and {@link D}.
*/
private class CD implements StaticMockFixture {
@Override
public StaticMockitoSessionBuilder setUpMockedClasses(
StaticMockitoSessionBuilder sessionBuilder) {
sessionBuilder.spyStatic(C.class);
sessionBuilder.spyStatic(D.class);
return sessionBuilder;
}
@Override
public void setUpMockBehaviors() {
ExtendedMockito.doAnswer(invocation -> {
mCollected.add("C.a");
return null;
}).when(C::a);
ExtendedMockito.doAnswer(invocation -> {
mCollected.add("C.b");
return null;
}).when(C::b);
ExtendedMockito.doAnswer(invocation -> {
mCollected.add("D.a");
return null;
}).when(D::a);
ExtendedMockito.doAnswer(invocation -> {
mCollected.add("D.b");
return null;
}).when(D::b);
}
@Override
public void tearDown() {
}
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.testables;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
/**
* Provides support for a set of static mocks for use within a single shared
* {@link StaticMockitoSession}.
*/
public interface StaticMockFixture {
/**
* Adds any required mock or spy classes managed by this {@link StaticMockFixture} to the
* {@link StaticMockitoSessionBuilder} provided.
*
* Call this to set up the classes that this expects to be mocked, by adding them to the
* {@link StaticMockitoSessionBuilder} using
* {@link StaticMockitoSessionBuilder#mockStatic(Class)},
* {@link StaticMockitoSessionBuilder#spyStatic(Class)} or similar as appropriate.
*
* @param sessionBuilder the {@link StaticMockitoSessionBuilder} to which the classes should be
* added to mock, spy, or otherwise as required
* @return sessionBuilder, to allow for fluent programming
*/
StaticMockitoSessionBuilder setUpMockedClasses(StaticMockitoSessionBuilder sessionBuilder);
/**
* Configures the behaviours of any mock or spy classes managed by this
* {@link StaticMockFixture}.
*
* Call this after {@link StaticMockitoSessionBuilder#startMocking()} has been called.
* This sets up any default behaviors for the mocks, spys, etc.
*/
void setUpMockBehaviors();
/**
* Tear everything down.
*/
void tearDown();
}

View File

@@ -0,0 +1,135 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.testables;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
import org.junit.rules.TestRule;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import org.mockito.quality.Strictness;
import java.util.function.Supplier;
/**
* <p>StaticMockFixtureRule is a {@link TestRule} that wraps one or more {@link StaticMockFixture}s
* to set them up and tear it down automatically. This works well when you have no other static
* mocks than the ones supported by their respective {@link StaticMockFixture}s.</p>
*
* <p>StaticMockFixtureRule should be defined as a rule on your test so it can clean up after
* itself. Like the following:</p>
* <pre class="prettyprint">
* public final StaticMockFixture mStaticMockFixtures = ...;
* &#064;Rule
* public final StaticMockFixtureRule mStaticMockFixtureRule =
* new StaticMockFixtureRule(mStaticMockFixtures);
* </pre>
*/
public class StaticMockFixtureRule implements TestRule {
private StaticMockitoSession mMockitoSession;
private StaticMockFixture[] mStaticMockFixtures;
private Supplier<? extends StaticMockFixture>[] mSupplier;
/**
* Constructs a StaticMockFixtureRule that always uses the same {@link StaticMockFixture}
* instance(s).
*
* @param staticMockFixtures the {@link StaticMockFixture}(s) to use.
*/
public StaticMockFixtureRule(StaticMockFixture... staticMockFixtures) {
mStaticMockFixtures = staticMockFixtures;
mSupplier = null;
}
/**
* Constructs a StaticMockFixtureRule that retrieves a new {@link StaticMockFixture} instance
* from one or more {@link Supplier<? extends StaticMockFixture >}s for each test invocation.
*
* @param supplier the {@link Supplier<? extends StaticMockFixture >}(s) that will supply the
* {@link StaticMockFixture}(s).
*/
@SafeVarargs
public StaticMockFixtureRule(Supplier<? extends StaticMockFixture>... supplier) {
mStaticMockFixtures = null;
mSupplier = supplier;
}
@Override
public Statement apply(Statement base, Description description) {
StaticMockitoSessionBuilder sessionBuilder = getSessionBuilder();
if (mSupplier != null) {
mStaticMockFixtures = new StaticMockFixture[mSupplier.length];
for (int i = 0; i < mSupplier.length; i++) {
mStaticMockFixtures[i] = mSupplier[i].get();
}
}
for (int i = 0; i < mStaticMockFixtures.length; i++) {
sessionBuilder = mStaticMockFixtures[i].setUpMockedClasses(sessionBuilder);
}
mMockitoSession = sessionBuilder.startMocking();
for (int i = 0; i < mStaticMockFixtures.length; i++) {
mStaticMockFixtures[i].setUpMockBehaviors();
}
return new TestWatcher() {
@Override
protected void succeeded(Description description) {
tearDown(null);
}
@Override
protected void failed(Throwable e, Description description) {
tearDown(e);
}
}.apply(base, description);
}
/**
* This allows overriding the creation of the builder for a new {@link StaticMockitoSession}.
* Mainly for testing, but also useful if you have other requirements for the session.
*
* @return a new {@link StaticMockitoSessionBuilder}.
*/
public StaticMockitoSessionBuilder getSessionBuilder() {
return mockitoSession().strictness(Strictness.LENIENT);
}
private void tearDown(Throwable e) {
mMockitoSession.finishMocking(e);
for (int i = mStaticMockFixtures.length - 1; i >= 0; i--) {
mStaticMockFixtures[i].tearDown();
if (mSupplier != null) {
mStaticMockFixtures[i] = null;
}
}
if (mSupplier != null) {
mStaticMockFixtures = null;
}
mMockitoSession = null;
}
}

View File

@@ -0,0 +1,186 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.testables;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import static org.mockito.quality.Strictness.LENIENT;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.Description;
import org.junit.runner.RunWith;
import org.junit.runners.model.Statement;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoSession;
import java.util.function.Supplier;
/** Tests that StaticMockFixture manages fixtures and suppliers correctly. */
@RunWith(AndroidJUnit4.class)
@SmallTest
public class StaticMockFixtureRuleTest {
private MockitoSession mMockitoSession;
@Mock private StaticMockitoSessionBuilder mSessionBuilder;
@Mock private StaticMockitoSession mSession;
@Mock private StaticMockFixture mA1;
@Mock private StaticMockFixture mB1;
@Mock private StaticMockFixture mA2;
@Mock private StaticMockFixture mB2;
@Mock private Supplier<StaticMockFixture> mSupplyA;
@Mock private Supplier<StaticMockFixture> mSupplyB;
@Mock private Statement mStatement;
@Mock private Description mDescription;
@Before
public void setUp() throws Throwable {
mMockitoSession = Mockito.mockitoSession()
.strictness(LENIENT)
.initMocks(this)
.startMocking();
prepareMockBehaviours();
}
@After
public void tearDown() {
mMockitoSession.finishMocking();
}
private void prepareFixtureMocks(StaticMockFixture... mocks) {
for (StaticMockFixture mock : mocks) {
when(mock.setUpMockedClasses(any())).thenAnswer(
invocation -> invocation.getArgument(0));
doNothing().when(mock).setUpMockBehaviors();
}
}
private void prepareMockBehaviours() throws Throwable {
when(mSessionBuilder.startMocking()).thenReturn(mSession);
when(mSupplyA.get()).thenReturn(mA1, mA2);
when(mSupplyB.get()).thenReturn(mB1, mB2);
prepareFixtureMocks(mA1, mA2, mB1, mB2);
when(mA1.setUpMockedClasses(any())).thenAnswer(invocation -> invocation.getArgument(0));
doNothing().when(mA1).setUpMockBehaviors();
when(mB1.setUpMockedClasses(any())).thenAnswer(invocation -> invocation.getArgument(0));
doNothing().when(mB1).setUpMockBehaviors();
doNothing().when(mStatement).evaluate();
doNothing().when(mA1).tearDown();
doNothing().when(mB1).tearDown();
}
private InOrder mocksInOrder() {
return inOrder(mSessionBuilder, mSession, mSupplyA, mSupplyB,
mA1, mA2, mB1, mB2, mStatement, mDescription);
}
private void verifyNoMoreImportantMockInteractions() {
verifyNoMoreInteractions(mSupplyA, mSupplyB, mA1, mA2, mB1, mB2, mStatement);
}
@Test
public void testRuleWorksWithExplicitFixtures() throws Throwable {
InOrder inOrder = mocksInOrder();
StaticMockFixtureRule rule = new StaticMockFixtureRule(mA1, mB1) {
@Override public StaticMockitoSessionBuilder getSessionBuilder() {
return mSessionBuilder;
}
};
Statement runMe = rule.apply(mStatement, mDescription);
inOrder.verify(mA1).setUpMockedClasses(any(StaticMockitoSessionBuilder.class));
inOrder.verify(mB1).setUpMockedClasses(any(StaticMockitoSessionBuilder.class));
inOrder.verify(mA1).setUpMockBehaviors();
inOrder.verify(mB1).setUpMockBehaviors();
runMe.evaluate();
inOrder.verify(mStatement).evaluate();
// note: tearDown in reverse order
inOrder.verify(mB1).tearDown();
inOrder.verify(mA1).tearDown();
// Round two: use the same fixtures again.
rule.apply(mStatement, mDescription).evaluate();
inOrder.verify(mA1).setUpMockedClasses(any(StaticMockitoSessionBuilder.class));
inOrder.verify(mB1).setUpMockedClasses(any(StaticMockitoSessionBuilder.class));
inOrder.verify(mA1).setUpMockBehaviors();
inOrder.verify(mB1).setUpMockBehaviors();
inOrder.verify(mStatement).evaluate();
// note: tearDown in reverse order
inOrder.verify(mB1).tearDown();
inOrder.verify(mA1).tearDown();
verifyNoMoreImportantMockInteractions();
}
@Test
public void testRuleWorksWithFixtureSuppliers() throws Throwable {
InOrder inOrder = mocksInOrder();
StaticMockFixtureRule rule = new StaticMockFixtureRule(mSupplyA, mSupplyB) {
@Override public StaticMockitoSessionBuilder getSessionBuilder() {
return mSessionBuilder;
}
};
Statement runMe = rule.apply(mStatement, mDescription);
inOrder.verify(mSupplyA).get();
inOrder.verify(mSupplyB).get();
inOrder.verify(mA1).setUpMockedClasses(any(StaticMockitoSessionBuilder.class));
inOrder.verify(mB1).setUpMockedClasses(any(StaticMockitoSessionBuilder.class));
inOrder.verify(mA1).setUpMockBehaviors();
inOrder.verify(mB1).setUpMockBehaviors();
runMe.evaluate();
inOrder.verify(mStatement).evaluate();
// note: tearDown in reverse order
inOrder.verify(mB1).tearDown();
inOrder.verify(mA1).tearDown();
// Round two: use the same suppliers again to retrieve different fixtures: mA2 and mB2
rule.apply(mStatement, mDescription).evaluate();
inOrder.verify(mSupplyA).get();
inOrder.verify(mSupplyB).get();
inOrder.verify(mA2).setUpMockedClasses(any(StaticMockitoSessionBuilder.class));
inOrder.verify(mB2).setUpMockedClasses(any(StaticMockitoSessionBuilder.class));
inOrder.verify(mA2).setUpMockBehaviors();
inOrder.verify(mB2).setUpMockBehaviors();
inOrder.verify(mStatement).evaluate();
// note: tearDown in reverse order
inOrder.verify(mB2).tearDown();
inOrder.verify(mA2).tearDown();
verifyNoMoreImportantMockInteractions();
}
}

View File

@@ -17,7 +17,6 @@
package com.android.server.testables;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -31,14 +30,10 @@ import android.provider.DeviceConfig;
import android.provider.DeviceConfig.Properties;
import android.util.Pair;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
import org.junit.rules.TestRule;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import org.mockito.Mockito;
import org.mockito.quality.Strictness;
import org.mockito.stubbing.Answer;
import java.util.Collections;
@@ -48,20 +43,12 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
/**
* TestableDeviceConfig uses ExtendedMockito to replace the real implementation of DeviceConfig
* with essentially a local HashMap in the callers process. This allows for unit testing that do not
* modify the real DeviceConfig on the device at all.
*
* <p>TestableDeviceConfig should be defined as a rule on your test so it can clean up after itself.
* Like the following:</p>
* <pre class="prettyprint">
* &#064;Rule
* public final TestableDeviceConfig mTestableDeviceConfig = new TestableDeviceConfig();
* </pre>
* TestableDeviceConfig is a {@link StaticMockFixture} that uses ExtendedMockito to replace the real
* implementation of DeviceConfig with essentially a local HashMap in the callers process. This
* allows for unit testing that do not modify the real DeviceConfig on the device at all.
*/
public final class TestableDeviceConfig implements TestRule {
public final class TestableDeviceConfig implements StaticMockFixture {
private StaticMockitoSession mMockitoSession;
private Map<DeviceConfig.OnPropertiesChangedListener, Pair<String, Executor>>
mOnPropertiesChangedListenerMap = new HashMap<>();
private Map<String, String> mKeyValueMap = new ConcurrentHashMap<>();
@@ -73,14 +60,21 @@ public final class TestableDeviceConfig implements TestRule {
mKeyValueMap.clear();
}
/**
* {@inheritDoc}
*/
@Override
public Statement apply(Statement base, Description description) {
mMockitoSession = mockitoSession()
.initMocks(this)
.strictness(Strictness.LENIENT)
.spyStatic(DeviceConfig.class)
.startMocking();
public StaticMockitoSessionBuilder setUpMockedClasses(
StaticMockitoSessionBuilder sessionBuilder) {
sessionBuilder.spyStatic(DeviceConfig.class);
return sessionBuilder;
}
/**
* {@inheritDoc}
*/
@Override
public void setUpMockBehaviors() {
doAnswer((Answer<Void>) invocationOnMock -> {
String namespace = invocationOnMock.getArgument(0);
Executor executor = invocationOnMock.getArgument(1);
@@ -115,20 +109,15 @@ public final class TestableDeviceConfig implements TestRule {
String name = invocationOnMock.getArgument(1);
return mKeyValueMap.get(getKey(namespace, name));
}).when(() -> DeviceConfig.getProperty(anyString(), anyString()));
}
return new TestWatcher() {
@Override
protected void succeeded(Description description) {
mMockitoSession.finishMocking();
mOnPropertiesChangedListenerMap.clear();
}
@Override
protected void failed(Throwable e, Description description) {
mMockitoSession.finishMocking(e);
mOnPropertiesChangedListenerMap.clear();
}
}.apply(base, description);
/**
* {@inheritDoc}
*/
@Override
public void tearDown() {
clearDeviceConfig();
mOnPropertiesChangedListenerMap.clear();
}
private static String getKey(String namespace, String name) {
@@ -210,4 +199,22 @@ public final class TestableDeviceConfig implements TestRule {
return properties;
}
/**
* <p>TestableDeviceConfigRule is a {@link TestRule} that wraps a {@link TestableDeviceConfig}
* to set it up and tear it down automatically. This works well when you have no other static
* mocks.</p>
*
* <p>TestableDeviceConfigRule should be defined as a rule on your test so it can clean up after
* itself. Like the following:</p>
* <pre class="prettyprint">
* &#064;Rule
* public final TestableDeviceConfigRule mTestableDeviceConfigRule =
* new TestableDeviceConfigRule();
* </pre>
*/
public static class TestableDeviceConfigRule extends StaticMockFixtureRule {
public TestableDeviceConfigRule() {
super(TestableDeviceConfig::new);
}
}
}

View File

@@ -0,0 +1,172 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.testables;
import android.provider.DeviceConfig;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.List;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class TestableDeviceConfigAndOtherStaticMocksTest {
@Rule
public StaticMockFixtureRule mStaticMockFixtureRule =
new StaticMockFixtureRule(TestableDeviceConfig::new, AB::new, CD::new);
private List<String> mCollected;
@Test
public void testDeviceConfigAndOtherStaticMocks() throws Exception {
mCollected = new ArrayList<>();
int n = 0;
String namespace = "foo";
String flag = "bar";
String flagValue = "new value";
Assert.assertNull(DeviceConfig.getProperty(namespace, flag));
A.a();
verifyCollected(++n, "A.a");
DeviceConfig.setProperty(namespace, flag, flagValue, false);
D.b();
verifyCollected(++n, "D.b");
Assert.assertEquals(flagValue, DeviceConfig.getProperty(namespace, flag));
C.b();
verifyCollected(++n, "C.b");
B.a();
verifyCollected(++n, "B.a");
}
private void verifyCollected(int n, String last) {
Assert.assertEquals(n, mCollected.size());
Assert.assertEquals(last, mCollected.get(n - 1));
}
private static class A {
/* package */ static void a() {}
/* package */ static void b() {}
}
private static class B {
/* package */ static void a() {}
/* package */ static void b() {}
}
private static class C {
/* package */ static void a() {}
/* package */ static void b() {}
}
private static class D {
/* package */ static void a() {}
/* package */ static void b() {}
}
/**
* AB StaticMockFixture class that handles two mocked classes, {@link A} and {@link B}.
*/
private class AB implements StaticMockFixture {
@Override
public StaticMockitoSessionBuilder setUpMockedClasses(
StaticMockitoSessionBuilder sessionBuilder) {
sessionBuilder.spyStatic(A.class);
sessionBuilder.spyStatic(B.class);
return sessionBuilder;
}
@Override
public void setUpMockBehaviors() {
ExtendedMockito.doAnswer(invocation -> {
mCollected.add("A.a");
return null;
}).when(A::a);
ExtendedMockito.doAnswer(invocation -> {
mCollected.add("A.b");
return null;
}).when(A::b);
ExtendedMockito.doAnswer(invocation -> {
mCollected.add("B.a");
return null;
}).when(B::a);
ExtendedMockito.doAnswer(invocation -> {
mCollected.add("B.b");
return null;
}).when(B::b);
}
@Override
public void tearDown() {
}
}
/**
* AB StaticMockFixture class that handles two mocked classes, {@link C} and {@link D}.
*/
private class CD implements StaticMockFixture {
@Override
public StaticMockitoSessionBuilder setUpMockedClasses(
StaticMockitoSessionBuilder sessionBuilder) {
sessionBuilder.spyStatic(C.class);
sessionBuilder.spyStatic(D.class);
return sessionBuilder;
}
@Override
public void setUpMockBehaviors() {
ExtendedMockito.doAnswer(invocation -> {
mCollected.add("C.a");
return null;
}).when(C::a);
ExtendedMockito.doAnswer(invocation -> {
mCollected.add("C.b");
return null;
}).when(C::b);
ExtendedMockito.doAnswer(invocation -> {
mCollected.add("D.a");
return null;
}).when(D::a);
ExtendedMockito.doAnswer(invocation -> {
mCollected.add("D.b");
return null;
}).when(D::b);
}
@Override
public void tearDown() {
}
}
}

View File

@@ -45,7 +45,8 @@ public class TestableDeviceConfigTest {
private static final long WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS = 2000; // 2 sec
@Rule
public TestableDeviceConfig mTestableDeviceConfig = new TestableDeviceConfig();
public TestableDeviceConfig.TestableDeviceConfigRule
mTestableDeviceConfig = new TestableDeviceConfig.TestableDeviceConfigRule();
@Test
public void getProperty_empty() {