Skip to content
This repository has been archived by the owner on Apr 14, 2023. It is now read-only.

Introduction to Ironhide

Greg Sawers edited this page Feb 10, 2015 · 25 revisions

What is Ironhide?

Ironhide is an Object Oriented android testing framework, written in Java, built on the Google Espresso testing framework. Ironhide provides an easier to use framework by handling some of the more complex elements of Espresso and Hamcrest matchers in the background, and providing the user with an easy way to create tests. A basic understanding of Google’s Espresso is recommended. Ironhide is also a badass transforming robot.

How does it work?

Ironhide is composed of Models and Page Elements. Models represent the various pages in your application. These Models are in turn populated by Page Elements that represent the various views and elements on the page. A test built with Ironhide will give the starting model and then chain together actions or assertions taken on page elements. Because of the nature of Ironhide, almost all tests can be made in one chain of actions, which results in tests being written much like they might be spoken. Here is an example of what a simple test might look like:

@Test
public void testgetToAddClientActivity(){
SchedulePage
	.NavDrawerButtonIcon.pause()
	.NavDrawerButton.click()
	.ClientsButton.click()
	.SearchClientField.pause()
	.AddClientButton.click()
	.FirstNameField.isDisplayed()
	.LastNameField.isDisplayed()
  ;
}

This test does the following:

  • Starts on the SchedulePage
  • Pauses to allow the page to load.
  • Clicks on the Navigation Drawer button to open the Navigation Drawer
  • Clicks on the Clients button in the navigation drawer.
  • Wait for the clients page to load (by pausing on the SearchClientField).
  • Click on the Add Client button.
  • Checks if the FirstNameField is displayed.
  • Checks if the LastNameField is displayed.

The SchedulePage variable is the name of an instantiated Model. The first two elements, NavDrawerButtonIcon and NavDrawerButton, are page elements of this model. Each page element in turn returns the model that will be on the screen when the item is interacted with. For example the NavDrawerButton, when interacted with, returns an instantiation of the NavigationDrawerModel, and allows the test to flow smoothly into using the elements found on that page. Finally, when the Add Client Page is reached at the end of the test, after the AddClientButton is clicked, the test checks to make sure everything is right with the page. If any point any of these click actions failed, or the First or LastNameField items weren’t displayed, the test would fail and Ironhide would move on to the next test.

Here are the various types in Ironhide you will be working with.

Page Elements: Representations of the various objects that might be found on one page of an application. These include:

  • Clickable: Basic, general, elements on the screen. Essentially anything on the screen that all you can do is click on it. Typically the most used element in Ironhide.
  • TextField: Simple element that represents a simple element that allows text interaction. Any sort of text box with an editable text field is represented by this type.
  • ListItem: Simple element that represents a single item inside a ListView with an adapter. Always obtained from a ListAdapter Element.
  • ListAdapter: Complex element that allows interaction with a ListView with an adapter. Used to obtain individual ListItems inside a ListView. Only use when dealing with a ListView that has an adapter.
  • DynamicListAdapter: Simple element that allows interaction with ListViews that have children added dynamically. Used to access ListItems inside a ListView that does not use an adapter. Should be used sparingly as these types of ListViews are considered bad practice.
  • Swipeable: Simple element that allows a swipe to be performed on the screen. Use this when the main purpose of the element will be to swipe the screen.
  • DatePicker: Simple element that allows interaction with a DatePicker View.
  • Zoomable: Simple element that allows a zoom to be performed on the screen. Use this when the main purpose of the element will be to zoom in/out.
  • LayoutView: Defines a ViewGroup typically referenced as a Layout. Provides generic assertions and matchers for Layouts.
  • NavDrawer: An element used to interact with a Drawer Layout. Allows you to open and close the navigation drawer and check if the drawer is open or closed.
  • Recycler: Wrapper for RecyclerViewActions that act specifically on Recycler Views. ViewActions that interact with RecyclerViews act differently than AdapterViews, so it can’t be used in combination with onData(Matcher). Seldom used.

Most elements will be Clickables or TextFields. The next most frequent are ListAdapters, with all others being special use types to allow interaction with more specific types.

Here is an example of the declaration of a page element.

public Clickable<NavDrawerModel> NavDrawerButton = new Clickable<>
  (NavDrawerModel.class, android.R.id.home);

This is a clickable representing the button that brings you to the Navigation Drawer inside the Schedule Page. A couple things to notice: “Clickable<NavDrawerModel>” : The Type. Clickable<NavDrawerModel> indicates a Clickable object that returns the NavDrawerModel when interacted with.

“Clickable<>(NavDrawerModel.class, android.R.id.home)”: The constructor. Again NavDrawerModel.class indicates the model to return to. “android.R.id.home” is a unique identifier for this object. Any resource id or unique Hamcrest matcher will work here. This allows Ironhide and Espresso to actually find the object you want to interact with.

The generic types of all Page Elements is the bread and butter of Ironhide. Everytime you call a function on a page element, it will return the model specified by the generic type. So in the example above, any sort of action done on the NavDrawerButton will return the NavDrawerModel, even if that action does not get you to that page. This allows the nice chaining of Ironhide to happen. Quite often however you might not want to go where the generic takes you. When this happens use the goesTo(Class<E> type) method. This will give you a new instantiation of your page element that is identical in every way but its generic type. Use it in this way :

NavDrawerButton.goesTo(LoginModel.class).isDisplayed() This is an example of where you would want to use the goesTo() method. You are checking if the NavDrawerButton is displayed, but you don't yet want to interact with the NavDrawerModel, so you use goesTo() to keep the chain on the LoginModel.

Models: Representations of entire pages, populated with page elements. Most of the time these models are simple, consisting only of instantiations of each element that would be found on the page. Sometimes helper methods can be put in these models to perform actions unique to your application. Here is an example of a simple model:

public class LoginModel extends PageObject{

public Clickable<LoginModel> MindbodyExpressLogo = new Clickable<>(LoginModel.class, R.id.expressLogo);

public Clickable<FindYourBusinessModel> FindYourBusinessButton = new Clickable<>
					(FindYourBusinessModel.class, R.id.siteSelectButton);

public Clickable<LoginModel> FindYourBusinessSpinner = new Clickable<>(LoginModel.class, 
R.id.siteSelectSpinner);

public ListAdapter<LoginModel> BusinessSpinnerList = new 
ListAdapter<>( LinearLayout.class, R.id.businessSpinner);

public TextField<LoginModel> UsernameField = new TextField<>(LoginModel.class, 
R.id.username);

public TextField<LoginModel> PasswordField = new TextField<>(LoginModel.class, R.id.password);

public Clickable<LoginModel> LoginButton = new Clickable<>(LoginModel.class, R.id.loginButton);
}

This is the LoginModel for the Mindbody Express app. Each of the objects inside represent a different element found on the page that are used to interact with them. Notice how it is made of nothing but Page Elements, easy as that. All of these PageElements are the same except for one, the ListAdapter. In this case, the “LinearLayout.class” does not represent the model to return to when interacted with, but instead it represents the itemType of the elements contained within the ListAdapter. That is to say, the ListItems of the BusinessSpinnerList are of type LinearLayout, so we know to expect a LinearLayout when we get items from the spinner.

A few more things you will need to get Ironhide up and running are:

  • A Test Fixture: To make your life easier, you are going to want a test fixture that contains instantiations of each of your models. Each of your test classes should extend this fixture. In the example test up above, instead of writing “ScheduleModel SchedulePage = new ScheduleModel()” before every test, we simply did this in the test fixture and never needed to instantiate a ScheduleModel variable again. Inside the test fixture you can also create methods to execute common chains of commands, such as logging in or logging out of the application, or variables to hold information like a username or password that might be common to all tests. You’re test fixture should construction should be similar to the following:

    public class TestFixture<T extends Activity> extends BaseInstrumentTestCase<T>

  • Notice: Your test fixture must be generic extending an Activity. This is so when you write your test classes you specify the Activity to start the tests on, which gets passed up to the BaseInstrumentTestCase. Also your test fixture must extend the BaseInstrumentTestCase. This class is a child of the android ActivityInstrumentationTestCase2 which interprets the espresso commands leveraged by Ironhide into actions on the device.

  • Test classes: This is where you’re actual tests should go. They should all extend the test fixture, and should have the generic type of whatever activity you wish to start on Each should have the following format :

public class Tests extends AppTestFixture<ActivityToStartTestsOn>{

  • From there you just need to write your tests inside various methods that start with the word “test”. Finally to run your tests, create a new run configuration specifying the method, class, or package of classes to run. Be sure to include “android.support.test.runner.AndroidJUnitRunner” as your test runner in your test configurations and gradle build files. Tests have three types of testing available to them: Actions, Matchers, and Assertions. “Actions” are actions you would use in the app, such as clicking, typing text, or pressing the back button. “Matchers” are ways to check the content of pages or page elements. Examples are withId(), isDisplayed(), isClickable(), amongst others. “Assertions” are ways to check attributes of pages or page elements. These can be used the check the position of an object relative to another, or if the element is a descendant of another element, etc. Reference the JavaDocs to see all the various Actions, Matchers, and Assertions available to you (nearly every one available to you in Espresso is available to you in Ironhide).

Its as easy as that! You can find the source code at github.mindbody.??? if you wish to dig around, otherwise you can find a jar that plugs into your app at github.mindbody.???, and remember you should always get tested.

Here is a helpful resource for some of the more advanced Hamcrest matchers https://code.google.com/p/android-test-kit/wiki/EspressoSamples

Clone this wiki locally