Merge "Support handling multiple static mocks."
This commit is contained in:
committed by
Android (Google) Code Review
commit
7baed4ac29
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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 = ...;
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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">
|
||||
* @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">
|
||||
* @Rule
|
||||
* public final TestableDeviceConfigRule mTestableDeviceConfigRule =
|
||||
* new TestableDeviceConfigRule();
|
||||
* </pre>
|
||||
*/
|
||||
public static class TestableDeviceConfigRule extends StaticMockFixtureRule {
|
||||
public TestableDeviceConfigRule() {
|
||||
super(TestableDeviceConfig::new);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user