diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AppCompactorTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AppCompactorTest.java index 475901a75d0dd..4a33739a8596b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/AppCompactorTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/AppCompactorTest.java @@ -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() { diff --git a/services/tests/mockingservicestests/src/com/android/server/testables/MultipleStaticMocksTest.java b/services/tests/mockingservicestests/src/com/android/server/testables/MultipleStaticMocksTest.java new file mode 100644 index 0000000000000..c0ab70a57327f --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/testables/MultipleStaticMocksTest.java @@ -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 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() { + + } + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/testables/StaticMockFixture.java b/services/tests/mockingservicestests/src/com/android/server/testables/StaticMockFixture.java new file mode 100644 index 0000000000000..0303fe1e1eb05 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/testables/StaticMockFixture.java @@ -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(); +} diff --git a/services/tests/mockingservicestests/src/com/android/server/testables/StaticMockFixtureRule.java b/services/tests/mockingservicestests/src/com/android/server/testables/StaticMockFixtureRule.java new file mode 100644 index 0000000000000..8e9d7ee71df3b --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/testables/StaticMockFixtureRule.java @@ -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; + +/** + *

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.

+ * + *

StaticMockFixtureRule should be defined as a rule on your test so it can clean up after + * itself. Like the following:

+ *
+*  public final StaticMockFixture mStaticMockFixtures = ...;
+ * @Rule
+ * public final StaticMockFixtureRule mStaticMockFixtureRule =
+ *     new StaticMockFixtureRule(mStaticMockFixtures);
+ * 
+ */ +public class StaticMockFixtureRule implements TestRule { + private StaticMockitoSession mMockitoSession; + private StaticMockFixture[] mStaticMockFixtures; + private Supplier[] 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}s for each test invocation. + * + * @param supplier the {@link Supplier}(s) that will supply the + * {@link StaticMockFixture}(s). + */ + @SafeVarargs + public StaticMockFixtureRule(Supplier... 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; + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/testables/StaticMockFixtureRuleTest.java b/services/tests/mockingservicestests/src/com/android/server/testables/StaticMockFixtureRuleTest.java new file mode 100644 index 0000000000000..b7e71ded30ab6 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/testables/StaticMockFixtureRuleTest.java @@ -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 mSupplyA; + @Mock private Supplier 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(); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfig.java b/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfig.java index 6d8fb731df9ff..32631be3ec24b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfig.java +++ b/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfig.java @@ -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. - * - *

TestableDeviceConfig should be defined as a rule on your test so it can clean up after itself. - * Like the following:

- *
- * @Rule
- * public final TestableDeviceConfig mTestableDeviceConfig = new TestableDeviceConfig();
- * 
+ * 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> mOnPropertiesChangedListenerMap = new HashMap<>(); private Map 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) 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; } + /** + *

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.

+ * + *

TestableDeviceConfigRule should be defined as a rule on your test so it can clean up after + * itself. Like the following:

+ *
+     * @Rule
+     * public final TestableDeviceConfigRule mTestableDeviceConfigRule =
+     *     new TestableDeviceConfigRule();
+     * 
+ */ + public static class TestableDeviceConfigRule extends StaticMockFixtureRule { + public TestableDeviceConfigRule() { + super(TestableDeviceConfig::new); + } + } } diff --git a/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfigAndOtherStaticMocksTest.java b/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfigAndOtherStaticMocksTest.java new file mode 100644 index 0000000000000..9616d13e42fea --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfigAndOtherStaticMocksTest.java @@ -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 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() { + + } + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfigTest.java b/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfigTest.java index 3eb72097182dc..d76c9388cacfc 100644 --- a/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfigTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfigTest.java @@ -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() {