blob: a2fa95deaa6077f81e42bd67fa5138da3f19049c [file] [log] [blame]
Jason Monk77f1b052017-05-02 14:22:21 -04001/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5 * except in compliance with the License. You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the
10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11 * KIND, either express or implied. See the License for the specific language governing
12 * permissions and limitations under the License.
13 */
14
15package android.testing;
16
17import static org.mockito.Mockito.mock;
18import static org.mockito.Mockito.withSettings;
19
20import android.content.Context;
21import android.content.res.Resources;
22import android.util.Log;
23import android.util.SparseArray;
24
25import org.mockito.invocation.InvocationOnMock;
26
27/**
28 * Provides a version of Resources that defaults to all existing resources, but can have ids
29 * changed to return specific values.
30 * <p>
31 * TestableResources are lazily initialized, be sure to call
32 * {@link TestableContext#ensureTestableResources} before your tested code has an opportunity
33 * to cache {@link Context#getResources}.
34 * </p>
35 */
36public class TestableResources {
37
38 private static final String TAG = "TestableResources";
39 private final Resources mResources;
40 private final SparseArray<Object> mOverrides = new SparseArray<>();
41
42 TestableResources(Resources realResources) {
43 mResources = mock(Resources.class, withSettings()
44 .spiedInstance(realResources)
45 .defaultAnswer(this::answer));
46 }
47
48 /**
49 * Gets the implementation of Resources that will return overridden values when called.
50 */
51 public Resources getResources() {
52 return mResources;
53 }
54
55 /**
56 * Sets the return value for the specified resource id.
57 * <p>
58 * Since resource ids are unique there is a single addOverride that will override the value
59 * whenever it is gotten regardless of which method is used (i.e. getColor or getDrawable).
60 * </p>
61 * @param id The resource id to be overridden
62 * @param value The value of the resource, null to cause a {@link Resources.NotFoundException}
63 * when gotten.
64 */
65 public void addOverride(int id, Object value) {
66 mOverrides.put(id, value);
67 }
68
69 /**
70 * Removes the override for the specified id.
71 * <p>
72 * This should be called over addOverride(id, null), because specifying a null value will
73 * cause a {@link Resources.NotFoundException} whereas removing the override will actually
74 * switch back to returning the default/real value of the resource.
75 * </p>
76 * @param id
77 */
78 public void removeOverride(int id) {
79 mOverrides.remove(id);
80 }
81
82 private Object answer(InvocationOnMock invocationOnMock) throws Throwable {
83 try {
84 int id = invocationOnMock.getArgument(0);
85 int index = mOverrides.indexOfKey(id);
86 if (index >= 0) {
87 Object value = mOverrides.valueAt(index);
88 if (value == null) throw new Resources.NotFoundException();
89 return value;
90 }
91 } catch (Resources.NotFoundException e) {
92 // Let through NotFoundException.
93 throw e;
94 } catch (Throwable t) {
95 // Generic catching for the many things that can go wrong, fall back to
96 // the real implementation.
97 Log.i(TAG, "Falling back to default resources call " + t);
98 }
99 return invocationOnMock.callRealMethod();
100 }
101}