Green Coffee - Running Gherkin Tests for Android

Mauricio Togneri, https://mauriciotogneri.com/, @mauriciotogneri

Green Coffee is an open source library that allows you to run your acceptance tests written in Gherkin in your Android instrumentation tests using the step definitions that you declare. Gherkin is a Business Readable, Domain Specific Language that lets you describe software's behavior without detailing how that behavior is implemented. This allows to apply a Behavior-Driven Development (BDD) approach and Agile testing to mobile software development.

Website: https://github.com/mauriciotogneri/green-coffee

License & Pricing: CasperJS is licensed under the MIT License

Support: https://github.com/mauriciotogneri/green-coffee/issues

Example

This page contains a small example so you can have a glimpse of how to use and what is possible to do with the library. Given the following feature written in Gherkin:

First, create a class that extends from GreenCoffeeTest and declare the Activity, the feature and the step definitions that will be used:

@RunWith(Parameterized.class) public class LoginFeatureTest extends GreenCoffeeTest { @Rule public ActivityTestRule activity = new ActivityTestRule<>(LoginActivity.class); public LoginFeatureTest(ScenarioConfig scenarioConfig) { super(scenarioConfig); } @Parameters(name = "{0}") public static Iterable scenarios() throws IOException { return new GreenCoffeeConfig(true) // automatically take a screenshot if a test fails .withFeatureFromAssets("assets/login.feature") .scenarios( new Locale("en", "GB"), new Locale("es", "ES") ); // the locales used to run the scenarios (optional) } @Test public void test() { start(new LoginSteps()); } }

If no locales are defined, the default one will be used. Next, create a class containing the steps definitions:

public class LoginSteps extends GreenCoffeeSteps { @Given("^I see an empty login form$") public void iSeeAnEmptyLoginForm() { onViewWithId(R.id.login_input_username).isEmpty(); onViewWithId(R.id.login_input_password).isEmpty(); } @When("^I introduce an invalid username$") public void iIntroduceAnInvalidUsername() { onViewWithId(R.id.login_input_username).type("guest"); } @When("^I introduce an invalid password$") public void iIntroduceAnInvalidPassword() { onViewWithId(R.id.login_input_password).type("1234"); } @When("^I press the login button$") public void iPressTheLoginButton() { onViewWithId(R.id.login_button_doLogin).click(); } @Then("^I see an error message saying 'Invalid credentials'$") public void iSeeAnErrorMessageSayingInvalidCredentials() { onViewWithText(R.string.login_credentials_error).isDisplayed(); } }

And that's it, now you can create your own tests using Green Coffee. This is how it looks when you run a more complex test:

You can see an example applied to a full app here.

Installation

In order to use Green Coffee, add the following dependency to your build.gradle file:

dependencies { androidTestImplementation 'com.android.support.test:runner:1.0.1' androidTestImplementation 'com.mauriciotogneri:greencoffee:3.2.1' }

defaultConfig { testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner' }

How it works

In essence, Green Coffee is no more than a Gherkin test runner that you can use for your Android instrumentation tests. To understand this better, let's see the main components involved in a test execution:

Feature

Features are written using the Gherkin language. Each feature consists of one or more scenarios that describe different situations in order to test that feature. Each scenario consists of steps that will simulate user interactions with the UI.

Test definition

A test definition is a class that extends from GreenCoffeeTest and declares the Activity, the feature and the step definitions that will be used during the test. You can find more information on Test definitons here.

Step definitions

The step definitions consist of set of classes that define all methods that will be used to match the steps in the scenarios involved in the test. You can find more information on Step definitons here.

Green Coffee is just the glue that interconnects these three components. This is how it works:

When we launch a instrumentation test, Green Coffee will read the feature declared in the test class, parse it and create the list of scenarios that will be used to run the tests For each scenario declared in the feature, the Android platform will automatically create an instance of the Activity that we declared in the test class and run a single test For each test executed, Green Coffee will automatically match each step in the corresponding scenario with the step definitions declared in the test class When a step in a scenario matches a method in the step definitions, Green Coffee will invoke that method with the corresponding parameters Each method invoked will either interact with the UI simulating a user interaction or verify that certain conditions are fulfilled

Espresso support

Although you can choose how to interact with the UI once a step definition is invoked, the library includes a set of methods that makes it more readable using Espresso. You can choose to use these helper methods, use directly Espresso or use any other mechanism.

A class that extends from GreenCoffeeSteps will have access to the following methods:

onViewWithId(int resourceId)

onViewWithId(@IdRes int resourceId, int index)

onViewWithText(int resourceId)

onViewWithText(@StringRes int resourceId, int index)

onViewWithText(String text)

onViewWithText(String text, int index)

onViewChildOf(@IdRes int parentViewId, int index)

withIndex(Matcher<View> matcher, int index)

nthChildOf(Matcher<View> parentMatcher, int childPosition)

waitFor(long value, TimeUnit timeUnit)

These methods are used to obtain a reference to an object that can perform operations on a UI component. For example:

@Given("...") public void someMethod() { onViewWithId(R.id.login_input_username); onViewWithText(R.string.login_credentials_error); onViewWithText("Some text"); }

All these methods return an object of the class ActionableView . This object can perform actions on the view or verify some conditions. Let's see some examples

@Given("...") public void someMethod() { ActionableView view = onViewWithId(...); // action view.click(); view.doubleClick(); view.longClick(); view.type(String text); view.clearText(); view.scrollTo(); view.swipeUp(); view.swipeDown(); view.swipeLeft(); view.swipeRight(); // verification view.isDisplayed(); view.isNotDisplayed(); view.isCompletelyDisplayed(); view.isEmpty(); view.isNotEmpty(); view.isSelected(); view.isNotSelected(); view.isChecked(); view.isNotChecked(); view.isFocusable(); view.isNotFocusable(); view.isClickable(); view.isEnabled(); view.isDisabled(); view.doesNotExist(); view.hasFocus(); view.doesNotHaveFocus(); view.hasErrorText(String text); view.contains(Object element); view.doesNotContain(Object element); hasDrawable(); doesNotHaveDrawable(); check(ViewAssertion viewAssertion); perform(ViewAction viewAction); }

If a verification is not fulfilled, an exception will be thrown and the test for the scenario will be marked as failed.

The class GreenCoffeeSteps also provides the following methods:

closeKeyboard() : closes the keyboard

: closes the keyboard pressBack() : simulates pressing the back button

: simulates pressing the back button string(@StringRes int key) : returns the string value for the given key

: returns the string value for the given key locale() : return the current Locale used in the test

: return the current Locale used in the test takeScreenshot(File file) : takes a screenshot and stores it in the given file

Lists

All the previous methods are used to act on UI components such as TextView , EditText , CheckBox , Button , etc. However, when it comes to interact with list views, we need to access elements inside of them in a different way.

Let's imagine we have a list view that displays objects of the class Contact . In order to obtain a reference on an element in the list, we have to create a custom matcher:

public class ContactMatcher extends DataMatcher { public ContactMatcher(int resourceId) { super(resourceId, Contact.class); } @Override public boolean matches(Contact contact, String content) { return contact.name().equals(content); } }

Then we can use it to obtain an object of the class ActionableData and perform operations on the matched UI component.

@Given("...") public void someMethod() { DataMatcher contactMatcher = new ContactMatcher(R.id.contacts_list_view); ActionableData data = contactMatcher.with("..."); data.click(); data.doubleClick(); data.longClick(); data.scrollTo(); }

Permissions

In order to accept permission during the execution of a test, the best option is to use the GrantPermissionRule .

Related Resources

This article was originally published in April 2018

Click here to view the complete list of tools reviews