package org.robolectric; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.internal.Instrument; import org.robolectric.internal.SandboxTestRunner; import org.robolectric.internal.bytecode.SandboxConfig; import org.robolectric.internal.bytecode.ShadowConstants; import org.robolectric.shadow.api.Shadow; import org.robolectric.testing.AnUninstrumentedClass; import org.robolectric.testing.Pony; @RunWith(SandboxTestRunner.class) public class ShadowingTest { @Test @SandboxConfig(shadows = {ShadowAccountManagerForTests.class}) public void testStaticMethodsAreDelegated() throws Exception { Object arg = mock(Object.class); AccountManager.get(arg); assertThat(ShadowAccountManagerForTests.wasCalled).isTrue(); assertThat(ShadowAccountManagerForTests.arg).isSameInstanceAs(arg); } @Implements(AccountManager.class) public static class ShadowAccountManagerForTests { public static boolean wasCalled = false; public static Object arg; public static AccountManager get(Object arg) { wasCalled = true; ShadowAccountManagerForTests.arg = arg; return mock(AccountManager.class); } } static class Context { } static class AccountManager { public static AccountManager get(Object arg) { return null; } } @Test @SandboxConfig(shadows = {ShadowClassWithProtectedMethod.class}) public void testProtectedMethodsAreDelegated() throws Exception { ClassWithProtectedMethod overlay = new ClassWithProtectedMethod(); assertEquals("shadow name", overlay.getName()); } @Implements(ClassWithProtectedMethod.class) public static class ShadowClassWithProtectedMethod { @Implementation protected String getName() { return "shadow name"; } } @Instrument public static class ClassWithProtectedMethod { protected String getName() { return "protected name"; } } @Test @SandboxConfig(shadows = {ShadowPaintForTests.class}) public void testNativeMethodsAreDelegated() throws Exception { Paint paint = new Paint(); paint.setColor(1234); assertThat(paint.getColor()).isEqualTo(1234); } @Instrument static class Paint { public native void setColor(int color); public native int getColor(); } @Implements(Paint.class) public static class ShadowPaintForTests { private int color; @Implementation protected void setColor(int color) { this.color = color; } @Implementation protected int getColor() { return color; } } @Implements(ClassWithNoDefaultConstructor.class) public static class ShadowForClassWithNoDefaultConstructor { public static boolean shadowDefaultConstructorCalled = false; public static boolean shadowDefaultConstructorImplementorCalled = false; public ShadowForClassWithNoDefaultConstructor() { shadowDefaultConstructorCalled = true; } @Implementation protected void __constructor__() { shadowDefaultConstructorImplementorCalled = true; } } @Instrument @SuppressWarnings({"UnusedDeclaration"}) public static class ClassWithNoDefaultConstructor { ClassWithNoDefaultConstructor(String string) { } } @Test @SandboxConfig(shadows = {Pony.ShadowPony.class}) public void directlyOn_shouldCallThroughToOriginalMethodBody() throws Exception { Pony pony = new Pony(); assertEquals("Fake whinny! You're on my neck!", pony.ride("neck")); assertEquals("Whinny! You're on my neck!", Shadow.directlyOn(pony, Pony.class).ride("neck")); assertEquals("Fake whinny! You're on my haunches!", pony.ride("haunches")); } @Test @SandboxConfig(shadows = {Pony.ShadowPony.class}) public void shouldCallRealForUnshadowedMethod() throws Exception { assertEquals("Off I saunter to the salon!", new Pony().saunter("the salon")); } static class TextView { } static class ColorStateList { public ColorStateList(int[][] ints, int[] ints1) { } } static class TypedArray { } @Implements(TextView.class) public static class TextViewWithDummyGetTextColorsMethod { public static ColorStateList getTextColors(Context context, TypedArray attrs) { return new ColorStateList(new int[0][0], new int[0]); } } @Test @SandboxConfig(shadows = ShadowOfClassWithSomeConstructors.class) public void shouldGenerateSeparatedConstructorBodies() throws Exception { ClassWithSomeConstructors o = new ClassWithSomeConstructors("my name"); assertNull(o.name); Method realConstructor = o.getClass().getDeclaredMethod(ShadowConstants.CONSTRUCTOR_METHOD_NAME, String.class); realConstructor.setAccessible(true); realConstructor.invoke(o, "my name"); assertEquals("my name", o.name); } @Instrument public static class ClassWithSomeConstructors { public String name; public ClassWithSomeConstructors(String name) { this.name = name; } } @Implements(ClassWithSomeConstructors.class) public static class ShadowOfClassWithSomeConstructors { @Implementation protected void __constructor__(String s) { } } @Test @SandboxConfig(shadows = {ShadowApiImplementedClass.class}) public void withNonApiSubclassesWhichExtendApi_shouldStillBeInvoked() throws Exception { assertEquals("did foo", new NonApiSubclass().doSomething("foo")); } public static class NonApiSubclass extends ApiImplementedClass { public String doSomething(String value) { return "did " + value; } } @Instrument public static class ApiImplementedClass { } @Implements(ApiImplementedClass.class) public static class ShadowApiImplementedClass { } @Test public void shouldNotInstrumentClassIfNotAddedToConfig() { assertEquals(1, new NonInstrumentedClass().plus(0)); } @Test @SandboxConfig(shadows = {ShadowNonInstrumentedClass.class}) public void shouldInstrumentClassIfAddedToConfig() { assertEquals(2, new NonInstrumentedClass().plus(0)); } public static class NonInstrumentedClass { public int plus(int x) { return x + 1; } } @Implements(NonInstrumentedClass.class) public static class ShadowNonInstrumentedClass { @Implementation protected int plus(int x) { return x + 2; } } @Test public void shouldNotInstrumentPackageIfNotAddedToConfig() throws Exception { Class clazz = Class.forName(AnUninstrumentedClass.class.getName()); assertTrue(Modifier.isFinal(clazz.getModifiers())); } @Test @SandboxConfig(instrumentedPackages = {"org.robolectric.testing"}) public void shouldInstrumentPackageIfAddedToConfig() throws Exception { Class clazz = Class.forName(AnUninstrumentedClass.class.getName()); assertFalse(Modifier.isFinal(clazz.getModifiers())); } }