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

Commit 77f1b05f authored by Jason Monk's avatar Jason Monk
Browse files

Add TestableResources

Makes it easy to add or change values of resources from tests.

Test: runtest --path frameworks/base/tests/testables/tests
Change-Id: Iaedff3d4ce9eaf9f270e7c62bc8c1634bd3519ec
parent 60e305ce
Loading
Loading
Loading
Loading
+30 −1
Original line number Diff line number Diff line
@@ -69,6 +69,7 @@ public class TestableContext extends ContextWrapper implements TestRule {
    private LeakCheck.Tracker mReceiver;
    private LeakCheck.Tracker mService;
    private LeakCheck.Tracker mComponent;
    private TestableResources mTestableResources;

    public TestableContext(Context base) {
        this(base, null);
@@ -98,9 +99,37 @@ public class TestableContext extends ContextWrapper implements TestRule {
        return super.getPackageManager();
    }

    /**
     * Makes sure the resources being returned by this TestableContext are a version of
     * TestableResources.
     * @see #getResources()
     */
    public void ensureTestableResources() {
        if (mTestableResources == null) {
            mTestableResources = new TestableResources(super.getResources());
        }
    }

    /**
     * Get (and create if necessary) {@link TestableResources} for this TestableContext.
     */
    public TestableResources getOrCreateTestableResources() {
        ensureTestableResources();
        return mTestableResources;
    }

    /**
     * Returns a Resources instance for the test.
     *
     * By default this returns the same resources object that would come from the
     * {@link ContextWrapper}, but if {@link #ensureTestableResources()} or
     * {@link #getOrCreateTestableResources()} has been called, it will return resources gotten from
     * {@link TestableResources}.
     */
    @Override
    public Resources getResources() {
        return super.getResources();
        return mTestableResources != null ? mTestableResources.getResources()
                : super.getResources();
    }

    public <T> void addMockSystemService(Class<T> service, T mock) {
+101 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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 android.testing;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.withSettings;

import android.content.Context;
import android.content.res.Resources;
import android.util.Log;
import android.util.SparseArray;

import org.mockito.invocation.InvocationOnMock;

/**
 * Provides a version of Resources that defaults to all existing resources, but can have ids
 * changed to return specific values.
 * <p>
 * TestableResources are lazily initialized, be sure to call
 * {@link TestableContext#ensureTestableResources} before your tested code has an opportunity
 * to cache {@link Context#getResources}.
 * </p>
 */
public class TestableResources {

    private static final String TAG = "TestableResources";
    private final Resources mResources;
    private final SparseArray<Object> mOverrides = new SparseArray<>();

    TestableResources(Resources realResources) {
        mResources = mock(Resources.class, withSettings()
                .spiedInstance(realResources)
                .defaultAnswer(this::answer));
    }

    /**
     * Gets the implementation of Resources that will return overridden values when called.
     */
    public Resources getResources() {
        return mResources;
    }

    /**
     * Sets the return value for the specified resource id.
     * <p>
     * Since resource ids are unique there is a single addOverride that will override the value
     * whenever it is gotten regardless of which method is used (i.e. getColor or getDrawable).
     * </p>
     * @param id The resource id to be overridden
     * @param value The value of the resource, null to cause a {@link Resources.NotFoundException}
     *              when gotten.
     */
    public void addOverride(int id, Object value) {
        mOverrides.put(id, value);
    }

    /**
     * Removes the override for the specified id.
     * <p>
     * This should be called over addOverride(id, null), because specifying a null value will
     * cause a {@link Resources.NotFoundException} whereas removing the override will actually
     * switch back to returning the default/real value of the resource.
     * </p>
     * @param id
     */
    public void removeOverride(int id) {
        mOverrides.remove(id);
    }

    private Object answer(InvocationOnMock invocationOnMock) throws Throwable {
        try {
            int id = invocationOnMock.getArgument(0);
            int index = mOverrides.indexOfKey(id);
            if (index >= 0) {
                Object value = mOverrides.valueAt(index);
                if (value == null) throw new Resources.NotFoundException();
                return value;
            }
        } catch (Resources.NotFoundException e) {
            // Let through NotFoundException.
            throw e;
        } catch (Throwable t) {
            // Generic catching for the many things that can go wrong, fall back to
            // the real implementation.
            Log.i(TAG, "Falling back to default resources call " + t);
        }
        return invocationOnMock.callRealMethod();
    }
}
+21 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
/**
 * Copyright (c) 2009, 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.
 */
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
    <string name="test_string">This is a test string</string>
</resources>
+93 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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 android.testing;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;

import android.content.res.Resources;
import android.support.test.InstrumentationRegistry;

import com.android.testables.R;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(AndroidTestingRunner.class)
public class TestableResourcesTest {

    @Rule
    public TestableContext mContext = new TestableContext(InstrumentationRegistry.getContext());

    @Test
    public void testLazyInit() {
        Resources before = mContext.getResources();
        mContext.ensureTestableResources();
        Resources after = mContext.getResources();
        assertNotEquals(before, after);
    }

    @Test
    public void testAddingResource() {
        final int nonExistentId = 3; // Ids don't go this low.

        try {
            mContext.getColor(nonExistentId);
            fail("Should throw NotFoundException");
        } catch (Resources.NotFoundException e) {
        }
        mContext.getOrCreateTestableResources().addOverride(nonExistentId, 0xffffff);

        assertEquals(0xffffff, mContext.getColor(nonExistentId));
    }

    @Test
    public void testClearingResource() {
        final int nonExistentId = 3; // Ids don't go this low.

        mContext.getOrCreateTestableResources().addOverride(nonExistentId, 0xffffff);
        assertEquals(0xffffff, mContext.getColor(nonExistentId));
        mContext.getOrCreateTestableResources().removeOverride(nonExistentId);
        try {
            mContext.getColor(nonExistentId);
            fail("Should throw NotFoundException");
        } catch (Resources.NotFoundException e) {
        }
    }

    @Test
    public void testOverrideExisting() {
        int existentId = R.string.test_string;

        assertNotNull(mContext.getString(existentId));
        mContext.getOrCreateTestableResources().addOverride(existentId, "Other strings");

        assertEquals("Other strings", mContext.getString(existentId));
    }

    @Test(expected = Resources.NotFoundException.class)
    public void testNonExistentException() {
        int existentId = R.string.test_string;

        assertNotNull(mContext.getString(existentId));
        mContext.getOrCreateTestableResources().addOverride(existentId, null);

        assertNull(mContext.getString(existentId));
    }
}
 No newline at end of file