Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7baed4ac authored by Christian Brunschen's avatar Christian Brunschen Committed by Android (Google) Code Review
Browse files

Merge "Support handling multiple static mocks."

parents 2fe0a501 58bfeec9
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -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() {
+163 −0
Original line number Diff line number Diff line
/*
 * 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() {

        }
    }
}
+56 −0
Original line number Diff line number Diff line
/*
 * 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();
}
+135 −0
Original line number Diff line number Diff line
/*
 * 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;
    }
}
+186 −0
Original line number Diff line number Diff line
/*
 * 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();
    }
}
Loading