Are you bound to flaky, slow Robolectric Tests? Do you want to know a way out of it? This session shows ideas and concepts to replace Robolectric from your test code base
14. Tired of issues like
java.lang.NullPointerException
at
org.robolectric.manifest.MetaData.
init(MetaData.java:55)
at
org.robolectric.manifest.AndroidMa
nifest.initMetaData(AndroidManifes
t.java:377)....
?
Don’t spent more time fixing
your test setup
than fixing your app
Sleepy by Tomas; flickr.com/photos/tma/2438467223; CC 2.0
15. What’s wrong with Robolectric?
• Developers used too much magic
forgot what unit test should be
• Your tests rely on correct 3rd party implementation
• Tests itself became flaky
16. Android today
• New developers follow MV* patterns
Code is designed to be testable
No need for Robolectric
• Older projects were made with Robolectric
• Projects not designed to be testable have often need for Robolectric
17. Welcome to Robolectric withdrawal
Day care:
• You have small units
Start removing
@RunWith(RobolectricTestrunner.class)
and we treat the few remaining
ambulant
Long term care:
• You have large units
• Use lots of magic
Mama will keep baby cozy and warm... by Oreste Messina; flickr.com/photos/oremessina/17338964228; CC 2.0
19. What about views?
• No one will parse your xml for you!
• when(activity.findViewById(R.id.toolbar))
.thenReturn(mock(Toolbar.class);
• What if class under test?
Spy it:
tested = spy(tested);
DIY Compost Bin: Assembly by WFIU Public Radio; flickr.com/photos/wfiupublicradio/5561442658; CC 2.0
20. What about views?
assertTrue(
myview.getVisibility() == View.VISIBLE)
What are we actually testing here?
Robolectrics View implementation!
What‘s the default value?
Don‘t test what you don‘t own!
verify(myview).setVisibility(View.VISIBLE))
Wrongside!byJérémyLelièvre;flickr.com/photos/jrmllvr/10887774436;CC2.0
21. What about Butterknife?
Couldn‘t be more easy:
fields are package protected
@BindView(R.id.title)
TextView title;
Just set them:
tested.title =
mock(TextView.class)
Butter by Joanna Bourne flickr.com/photos/66992990@N00/4819375090; CC 2.0
22. Testing Parcelation (Before)
@RunWith(RobolectricTestRunner.class)
public class UserTest {
Parcel parcel = Parcel.obtain();
User tested = new User("123", "456");
@Test
public void check_parcel_implementation() {
tested.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
User out = User.CREATOR.createFromParcel(parcel);
assertEquals("123", out.getUser());
assertEquals("456", out.getPassword());
}
23. Testing Parcelation (After)
Parcel parcel = mock(Parcel.class);
User tested = new User("123", "456");
@Test
public void should_read_from_parcel() {
when(parcel.readString()).thenReturn("123", "456");
User out = User.CREATOR.createFromParcel(parcel);
assertEquals("123", out.getUser());
assertEquals("456", out.getPassword());
}
24. Testing Parcelation (After)
Parcel parcel = mock(Parcel.class);
User tested = new User("123", "456");
@Test
public void should_write_to_parcel() {
tested.writeToParcel(parcel, 0);
InOrder verifier = inOrder(parcel);
verifier.verify(parcel).writeString("123");
verifier.verify(parcel).writeString("456");
verifier.verifyNoMoreInteractions();
}
25. Testing Parcelation (Alternative)
Tip 1: move your models AutoParcel
• No need for parcelation tests anymore
• https://github.com/frankiesardo/auto-parcel
Tip 2: move parcelation code to Parceler
• https://github.com/johncarl81/parceler
Parcels by delgrosso; flickr.com/photos/delgrossodotcom/2553424895; CC 2.0
26. Testing Intent building (Before)
@Test
public void should_create_intent() {
Intent intent = MyActivity.create(
mock(Context.class), deal);
assertEquals(
deal,
intent.getParcelableExtra(“DEAL“));
}
What are we actually testing here?
Robolectrics Intent implementation!
Dont test what you dont own!
28. Testing Intent building (After)
class IntentFactory {
public static IntentFactory instance = new IntentFactory();
Intent create(Context ctx, Class<? extends Context> clazz){
return new Intent(ctx, clazz);
}
}
Call from @After
public static void reset() {
instance = new IntentFactory();
}
29. Testing Intent building (after)
• Scared of the public instance
?
• Could be private but then
need setter or reflection
• Whom are you afraid of?
Dr. EVIL (Doctor Malito en Austin Powers) by Hersson Piratoba;
30. Testing intent building (after)
Do you know the rule about
encapsulation and tests?
Uh, no. What rule is that?
Tests trump Encapsulation.
What does that mean?
That means that tests win.
No test can be denied access to a variable
simply to maintain encapsulation.
Uncle Bob
https://blog.8thlight.com/uncle-bob/2015/06/30/the-little-
singleton.html
Why so serious? by SYD, MsSaraKelly;
flickr.com/photos/mssarakelly/14403017054, CC 2.0
31. If you still dont like it..
package protected and move mocking to a class in test in same package:
public class IntentFactoryMock {
public static void mockFactory(Intent intent) {
IntentFactory.instance = mockIntentFactory(intent);
}
public static void reset() {
IntentFactory.instance = new IntentFactory();
}
private static IntentFactory mockIntentFactory(Intent intent) {
IntentFactory factory = mock(IntentFactory.class);
when(factory.create()).thenReturn(intent);
32. Testing intent building (alternative)
• Check out Dart and Henson:
https://medium.com/groupon-
eng/better-android-intents-with-dart-
henson-1ca91793944b#.c7agm3ikh
33. Navigation
FragmentManager fragmentManager = mock(FragmentManager.class);
when(fragmentManager.beginTransaction()).thenReturn(transaction);
when(transaction.replace(anyInt(),
any(Fragment.class))).thenReturn(transaction);
when(transaction.replace(anyInt(), any(Fragment.class),
anyString())).thenReturn(transaction);
when(transaction.remove(any(Fragment.class))).thenReturn(transaction);
when(transaction.addToBackStack(anyString())).thenReturn(transaction);
when(transaction.add(anyInt(),
any(Fragment.class))).thenReturn(transaction);
when(transaction.add(anyInt(), any(Fragment.class),
anyString())).thenReturn(transaction);
when(transaction.setCustomAnimations(anyInt(),
anyInt())).thenReturn(transaction);
when(transaction.setCustomAnimations(anyInt(), anyInt(), anyInt(),
anyInt())).thenReturn(transaction);
Mocking fragment transactions sucks
Navigation by Marcus Ramberg; flickr.com/photos/marcusramberg/71281972; CC 2.0
34. Navigation by Marcus Ramberg; flickr.com/photos/marcusramberg/71281972; CC 2.0
Navigation
• Wrap navigation into a component:
@Inject
public Interactor(FragmentTransactionsUtil util)
{...}
util.addAllowingStateLoss(
getFragmengManager(), R.id.content,
MyFragment.create());
35. Dagger, Butterknife & co.
• Don‘t use Dagger in unit tests!
• Call the constructor yourself
Constructor injection is the only one you should use
• For activites, services, fragment with @Inject fields:
Set the instances directly, package protected!
• KISS: Keep it simple stupid!
36. Dagger, Butterknife & co.
• Never use Dagger.inject() and co directly!
• Wrap it
• Replace them in tests!
public final class Dependencies {
private static Injector instance = new DaggerInjector();
private static ViewBinder viewBinder = new ViewBinderWithButterknife();
private static ExtraBinder extraBinder = new ExtraBinderWithDart();
public static void bind(Activity activity) {...}
...
37. Butter your tests...
• Introducing Diacetyl
• ... is added to some foods to impart
its buttery flavor (wikipedia)
• It adds artifical butter flavor to your
Butterknife in test environments.
Pam Cooking Spray, Butter by Mike Mozart; flickr.com/photos/jeepersmedia/15203456322; CC 2.0
38. Butter your tests...
class MyButterKnifeActivity {
@Bind TextView textView;
@Bind EditText editText;
...
class MyButterKnifeActivityTest {
@Test
public void test() {
MyButterKnifeActivtiy tested = new
MyButterKnifeActivtiy();
Diacetyl.butterForTests(tested);
Pam Cooking Spray, Butter by Mike Mozart; flickr.com/photos/jeepersmedia/15203456322; CC 2.0
40. Version checks
public boolean isRTL() {
if (Build.VERSION.SDK_INT >=
Build.VERSION_CODES.JELLY_BEAN_MR1) {
return resources.getConfiguration()
.getLayoutDirection() == LAYOUT_DIRECTION_RTL;
}
return false;
}
41. Version checks (Before)
@Test
@Config(reportSdk = 16)
public void returns_false_on_pre_jellybeans() {
Resources resources =
Robolectric.buildActivity(Activity.class)
.get().getResources();
RTLUtil tested = new RTLUtil(resources);
assertFalse(tested.isRTL());
}
42. Version checks
• Use reflection?
*github.com/dpreussler/SuperReflect
Reflect.on(
Build.VERSION.class)
.set("SDK_INT", 14);
Better:
wrap all the things!
Reflection by Anderson Mancini; flickr.com/photos/ektogamat/3052020494; CC 2.0
43. Version checks (After)
public boolean isRTL() {
if (AndroidVersions.isMinJellyBeanMR1()) {
return resources
.getConfiguration().getLayoutDirection()
== View.LAYOUT_DIRECTION_RTL;
}
return false;
}
44. Version checks
@Test
public void returns_false_on_pre_jelly_beans() {
AndroidVersions.VERSION = 16;
Resources resources = mockResources();
RTLUtil tested = new RTLUtil(resources);
assertFalse(tested.isRTL());
verifyZeroInteractions(resources);
}
45. Version checks
public final class AndroidVersions {
public static int VERSION = Build.VERSION.SDK_INT;
public static boolean isMinJellyBean() {
return VERSION >= Build.VERSION_CODES.JELLY_BEAN;
}
...
}
Call from @After
public static void reset() {
VERSION = Build.VERSION.SDK_INT;
}
47. Testing asynchronicity (Before)
MyActivity activity =
Robolectric.buildActivity(MyActivity.class).get();
activity.doThingInBackground();
Robolectric.getForegroundThreadScheduler()
.advanceToNextPostedRunnable();
// check what happened in runnable
48. Testing asynchronicity (After)
MyActivity activity = spy(new MyActivity());
ArgumentCaptor<Runnable> captor =
ArgumentCaptor.forClass(Runnable.class);
activity.doThingInBackground();
verify(activity).runOnUiThread(captor.capture());
captor.getValue().run();
... Tip: split into 2 tests:
• verify(activity).runOnUiThread(anyRunnable())
• and the captor one that does the action
49. Testing asynchronicity
„Always wrap the system clock,
so it can be easily substituted
for testing“
(Martin Fowler)
http://martinfowler.com/articles/nonDeterminism.html#Time
Rollercoaster by Eric; flickr.com/photos/eric-omba/481762682; CC 2.0
50. Problem: Libraries
• Support libraries
• Third party libraries
• Implementation not
empty,
Run real code on tests
Delivery by Bill; flickr.com/photos/34639780@N07/16480925469; CC 2.0
52. Problem: Libraries
• Needs more mocking
• Might need Reflection work
• Tricky on constructors
i.e. custom views extends
„empty“ android views
Delivery by Bill; flickr.com/photos/34639780@N07/16480925469; CC 2.0
54. Write it nicer
• mockView() instead of mock(View.class)
...
• mockEditText(„test“)that implements Editable
• mockFragmentTransaction() that returns self while building
• mockRecyclerView() that remembers adapter
https://github.com/dpreussler/mockitoid
rainbow revisited by Bill Rosgen; flickr.com/photos/wrosgen/4706169184; CC 2.0
55. Write it nicer
• anyActivity() instead of any(Activity.class)
• anyContext()...
• anyView()...
https://github.com/dpreussler/mockitoid
rainbow revisited by Bill Rosgen; flickr.com/photos/wrosgen/4706169184; CC 2.0
56. Gradually stop your addiction
Subscription: Unmock
Decide in gradle which classes
you still need from Robolectric
https://github.com/bjoernQ/unmock-plugin
57. Any reasons to stick to Robolectric?
“I need to test lots of UI”
Test with mocks!
“My tests are testing a complete flow”
Your tests are too big
“My tests depend on resources”
Integration test? Move to Espresso?
58. Any reasons to stick to Robolectric?
“I want to test my SqlLiteOpenhelper with a real database?”
Wrap SqlLiteopenHelper!
Assert Query Strings
(Use a java version of SQLlite if you real queries)
No reason!
And often smell for bad code design! Fix the
code not the test!
59. Groupon is Mobile
One of worlds most popular apps
> 50 Mio android downloads
Unit testing by developers
> 2000 unit test for Consumer app
> 3000 unit tests for Merchant app
Automation by QA engineers
RoboRemote for Consumer app
Appium for Merchant app