1package org.robolectric;
2
3import static org.assertj.core.api.Assertions.assertThat;
4import static org.junit.Assert.assertEquals;
5import static org.junit.Assert.assertFalse;
6import static org.junit.Assert.assertNull;
7import static org.junit.Assert.assertTrue;
8import static org.mockito.Mockito.mock;
9
10import java.lang.reflect.Method;
11import java.lang.reflect.Modifier;
12import org.assertj.core.api.Assertions;
13import org.junit.Test;
14import org.junit.runner.RunWith;
15import org.robolectric.annotation.Implementation;
16import org.robolectric.annotation.Implements;
17import org.robolectric.annotation.internal.Instrument;
18import org.robolectric.internal.SandboxTestRunner;
19import org.robolectric.internal.bytecode.SandboxConfig;
20import org.robolectric.internal.bytecode.ShadowConstants;
21import org.robolectric.shadow.api.Shadow;
22import org.robolectric.testing.AnUninstrumentedClass;
23import org.robolectric.testing.Pony;
24
25@RunWith(SandboxTestRunner.class)
26public class ShadowingTest {
27
28  @Test
29  @SandboxConfig(shadows = {ShadowAccountManagerForTests.class})
30  public void testStaticMethodsAreDelegated() throws Exception {
31    Object arg = mock(Object.class);
32    AccountManager.get(arg);
33    assertThat(ShadowAccountManagerForTests.wasCalled).isTrue();
34    assertThat(ShadowAccountManagerForTests.arg).isSameAs(arg);
35  }
36
37  @Implements(AccountManager.class)
38  public static class ShadowAccountManagerForTests {
39    public static boolean wasCalled = false;
40    public static Object arg;
41
42    public static AccountManager get(Object arg) {
43      wasCalled = true;
44      ShadowAccountManagerForTests.arg = arg;
45      return mock(AccountManager.class);
46    }
47  }
48
49  static class Context {
50  }
51
52  static class AccountManager {
53    public static AccountManager get(Object arg) {
54      return null;
55    }
56  }
57
58  @Test
59  @SandboxConfig(shadows = {ShadowClassWithProtectedMethod.class})
60  public void testProtectedMethodsAreDelegated() throws Exception {
61    ClassWithProtectedMethod overlay = new ClassWithProtectedMethod();
62    assertEquals("shadow name", overlay.getName());
63  }
64
65  @Implements(ClassWithProtectedMethod.class)
66  public static class ShadowClassWithProtectedMethod {
67    @Implementation
68    protected String getName() {
69      return "shadow name";
70    }
71  }
72
73  @Instrument
74  public static class ClassWithProtectedMethod {
75    protected String getName() {
76      return "protected name";
77    }
78  }
79
80  @Test
81  @SandboxConfig(shadows = {ShadowPaintForTests.class})
82  public void testNativeMethodsAreDelegated() throws Exception {
83    Paint paint = new Paint();
84    paint.setColor(1234);
85
86    Assertions.assertThat(paint.getColor()).isEqualTo(1234);
87  }
88
89  @Instrument
90  static class Paint {
91    public native void setColor(int color);
92    public native int getColor();
93  }
94
95  @Implements(Paint.class)
96  public static class ShadowPaintForTests {
97    private int color;
98
99    @Implementation
100    protected void setColor(int color) {
101      this.color = color;
102    }
103
104    @Implementation
105    protected int getColor() {
106      return color;
107    }
108  }
109
110  @Implements(ClassWithNoDefaultConstructor.class)
111  public static class ShadowForClassWithNoDefaultConstructor {
112    public static boolean shadowDefaultConstructorCalled = false;
113    public static boolean shadowDefaultConstructorImplementorCalled = false;
114
115    public ShadowForClassWithNoDefaultConstructor() {
116      shadowDefaultConstructorCalled = true;
117    }
118
119    @Implementation
120    protected void __constructor__() {
121      shadowDefaultConstructorImplementorCalled = true;
122    }
123  }
124
125  @Instrument @SuppressWarnings({"UnusedDeclaration"})
126  public static class ClassWithNoDefaultConstructor {
127    ClassWithNoDefaultConstructor(String string) {
128    }
129  }
130
131  @Test
132  @SandboxConfig(shadows = {Pony.ShadowPony.class})
133  public void directlyOn_shouldCallThroughToOriginalMethodBody() throws Exception {
134    Pony pony = new Pony();
135
136    assertEquals("Fake whinny! You're on my neck!", pony.ride("neck"));
137    assertEquals("Whinny! You're on my neck!", Shadow.directlyOn(pony, Pony.class).ride("neck"));
138
139    assertEquals("Fake whinny! You're on my haunches!", pony.ride("haunches"));
140  }
141
142  @Test
143  @SandboxConfig(shadows = {Pony.ShadowPony.class})
144  public void shouldCallRealForUnshadowedMethod() throws Exception {
145    assertEquals("Off I saunter to the salon!", new Pony().saunter("the salon"));
146  }
147
148  static class TextView {
149  }
150
151  static class ColorStateList {
152    public ColorStateList(int[][] ints, int[] ints1) {
153    }
154  }
155
156  static class TypedArray {
157  }
158
159  @Implements(TextView.class)
160  public static class TextViewWithDummyGetTextColorsMethod {
161    public static ColorStateList getTextColors(Context context, TypedArray attrs) {
162      return new ColorStateList(new int[0][0], new int[0]);
163    }
164  }
165
166  @Test
167  @SandboxConfig(shadows = ShadowOfClassWithSomeConstructors.class)
168  public void shouldGenerateSeparatedConstructorBodies() throws Exception {
169    ClassWithSomeConstructors o = new ClassWithSomeConstructors("my name");
170    assertNull(o.name);
171
172    Method realConstructor = o.getClass().getDeclaredMethod(ShadowConstants.CONSTRUCTOR_METHOD_NAME, String.class);
173    realConstructor.setAccessible(true);
174    realConstructor.invoke(o, "my name");
175    assertEquals("my name", o.name);
176  }
177
178  @Instrument
179  public static class ClassWithSomeConstructors {
180    public String name;
181
182    public ClassWithSomeConstructors(String name) {
183      this.name = name;
184    }
185  }
186
187  @Implements(ClassWithSomeConstructors.class)
188  public static class ShadowOfClassWithSomeConstructors {
189    @Implementation
190    protected void __constructor__(String s) {
191    }
192  }
193
194  @Test
195  @SandboxConfig(shadows = {ShadowApiImplementedClass.class})
196  public void withNonApiSubclassesWhichExtendApi_shouldStillBeInvoked() throws Exception {
197    assertEquals("did foo", new NonApiSubclass().doSomething("foo"));
198  }
199
200  public static class NonApiSubclass extends ApiImplementedClass {
201    public String doSomething(String value) {
202      return "did " + value;
203    }
204  }
205
206  @Instrument
207  public static class ApiImplementedClass {
208  }
209
210  @Implements(ApiImplementedClass.class)
211  public static class ShadowApiImplementedClass {
212  }
213
214  @Test
215  public void shouldNotInstrumentClassIfNotAddedToConfig() {
216    assertEquals(1, new NonInstrumentedClass().plus(0));
217  }
218
219  @Test
220  @SandboxConfig(shadows = {ShadowNonInstrumentedClass.class})
221  public void shouldInstrumentClassIfAddedToConfig() {
222    assertEquals(2, new NonInstrumentedClass().plus(0));
223  }
224
225  public static class NonInstrumentedClass {
226    public int plus(int x) {
227      return x + 1;
228    }
229  }
230
231  @Implements(NonInstrumentedClass.class)
232  public static class ShadowNonInstrumentedClass {
233    @Implementation
234    protected int plus(int x) {
235      return x + 2;
236    }
237  }
238
239  @Test
240  public void shouldNotInstrumentPackageIfNotAddedToConfig() throws Exception {
241    Class<?> clazz = Class.forName(AnUninstrumentedClass.class.getName());
242    assertTrue(Modifier.isFinal(clazz.getModifiers()));
243  }
244
245  @Test
246  @SandboxConfig(instrumentedPackages = {"org.robolectric.testing"})
247  public void shouldInstrumentPackageIfAddedToConfig() throws Exception {
248    Class<?> clazz = Class.forName(AnUninstrumentedClass.class.getName());
249    assertFalse(Modifier.isFinal(clazz.getModifiers()));
250  }
251}
252