• Yaml file where a list of http “interactions” is recorded • Each test method corresponds to a tape file • Interactions are recorded in request-response pairs • Includes method, URL, status, headers and response body • Modes: read-only, read-write, write-only, etc. • Read from instrumentation APK “assets” directory • Writes to device’s external storage directory Tape WHERE INTERACTIONS HAPPEN
x Match Rules public enum MatchRules implements MatchRule { method { @Override public boolean isMatch(Request a, Request b) { return a.method().equalsIgnoreCase(b.method()); } }, uri { @Override public boolean isMatch(Request a, Request b) { return a.url().equals(b.url()); } }, host { @Override public boolean isMatch(Request a, Request b) { return a.url().url().getHost().equals(b.url().url().getHost()); } }, path { @Override public boolean isMatch(Request a, Request b) { return a.url().url().getPath().equals(b.url().url().getPath()); } }, port { @Override public boolean isMatch(Request a, Request b) { return a.url().url().getPort() == b.url().url().getPort(); } } }
Class hierarchy Message Request Response RecordedRequest RecordedResponse AbstractMessage RecordedMessage Concrete class Abstract class Interface MAIN ENTITIES
Usage @RunWith(AndroidJUnit4.class) public class ExampleInstrumentationTest { private final ActivityTestRule activityTestRule = new ActivityTestRule<>(MainActivity.class); private final OkReplayConfig configuration = new OkReplayConfig.Builder() .tapeRoot(new AndroidTapeRoot(getContext(), getClass())) .defaultMode(TapeMode.READ_WRITE) .interceptor(new OkReplayInterceptor()) .defaultMatchRules(MatchRules.host, MatchRules.path, MatchRules.method) .build(); @Rule public final TestRule testRule = new OkReplayRuleChain(configuration, activityTestRule).get(); @Test @OkReplay public void bar() { onView(withId(R.id.foo)).perform(click()); onView(withId(R.id.bar)).check(matches(withText(containsString("bar")))); } }
Usage @RunWith(AndroidJUnit4.class) public class ExampleInstrumentationTest { private final ActivityTestRule activityTestRule = new ActivityTestRule<>(MainActivity.class); private final OkReplayConfig configuration = new OkReplayConfig.Builder() .tapeRoot(new AndroidTapeRoot(getContext(), getClass())) .defaultMode(TapeMode.READ_WRITE) .interceptor(new OkReplayInterceptor()) .defaultMatchRules(MatchRules.host, MatchRules.path, MatchRules.method) .build(); @Rule public final TestRule testRule = new OkReplayRuleChain(configuration, activityTestRule).get(); @Test @OkReplay public void bar() { onView(withId(R.id.foo)).perform(click()); onView(withId(R.id.bar)).check(matches(withText(containsString("bar")))); } }
Usage @RunWith(AndroidJUnit4.class) public class ExampleInstrumentationTest { private final ActivityTestRule activityTestRule = new ActivityTestRule<>(MainActivity.class); private final OkReplayConfig configuration = new OkReplayConfig.Builder() .tapeRoot(new AndroidTapeRoot(getContext(), getClass())) .defaultMode(TapeMode.READ_WRITE) .interceptor(new OkReplayInterceptor()) .defaultMatchRules(MatchRules.host, MatchRules.path, MatchRules.method) .build(); @Rule public final TestRule testRule = new OkReplayRuleChain(configuration, activityTestRule).get(); @Test @OkReplay public void bar() { onView(withId(R.id.foo)).perform(click()); onView(withId(R.id.bar)).check(matches(withText(containsString("bar")))); } }
Usage @RunWith(AndroidJUnit4.class) public class ExampleInstrumentationTest { private final ActivityTestRule activityTestRule = new ActivityTestRule<>(MainActivity.class); private final OkReplayConfig configuration = new OkReplayConfig.Builder() .tapeRoot(new AndroidTapeRoot(getContext(), getClass())) .defaultMode(TapeMode.READ_WRITE) .interceptor(new OkReplayInterceptor()) .defaultMatchRules(MatchRules.host, MatchRules.path, MatchRules.method) .build(); @Rule public final TestRule testRule = new OkReplayRuleChain(configuration, activityTestRule).get(); @Test @OkReplay public void bar() { onView(withId(R.id.foo)).perform(click()); onView(withId(R.id.bar)).check(matches(withText(containsString("bar")))); } }
• Granting WRITE_EXTERNAL_STORAGE permission • Handling secrets • Re-recording tapes • If your test fails, you’ll need to re-record the tapes • Switching between replaying/recording modes Pitfalls
• Writing stable Espresso tests is hard • A lot of cool concepts can be brought in from other platforms • Naming is also hard • Writing stable and reproducible Android UI tests is possible Conclusion FINAL REMARKS