Testing Android apps
with (Robo)Spock

by Wojtek Erbetowski

And who are you?



Android developers,
Spock/Groovy users,
Lost,
Other.

About me


Wojtek Erbetowski

http://erbetowski.pl
@wojtassj

Team


tech lead @ Polidea

Community


Warsaw JUG co-leader
Warsjawa 2012
Warsaw CodeRetreat 2013
NameCollision hackathon 2013
Git kata 2013

MobileWarsaw (coming, April, 22nd)

Goals


  • Build theoretical background for Android Unit Testing
  • Discuss difficulties of running Android unit tests
  • Present Spock and Robolectric to Android devs

Testing Android

Fundamentals

Project Structure

MyProject/
  AndroidManifest.xml
    res/
      ... (resources for main application)
    src/
      ... (source code for main application) ...
    tests/
      AndroidManifest.xml
      res/
        ... (resources for tests)
      src/
        ... (source code for tests)

Life cycle


  1. Edit the code
  2. Redeploy applications to emulator
  3. Launch tests

Potential problems

  • Continuous Integration
  • Parallelization
  • Slow
  • Java!

Sample test


public class ScanExampleTest
        extends ActivityInstrumentationTestCase2<MyScanActivity> {

    private Solo solo;

    public ScanExampleTest() {
        super("org.my.scanExample", MyScanActivity.class);
    }

    @Override
    public void setUp() throws Exception {
        solo = new Solo(getInstrumentation(), getActivity());
    }

    public void testManualEntry() throws Exception {
        solo.assertCurrentActivity("expecting example app", MyScanActivity.class);

        boolean ableToScan = CardIOActivity.canReadCardWithCamera(solo.getCurrentActivity());

        solo.clickOnButton(0);

        if (ableToScan) {
        // some devices don't support use of the camera.

            solo.assertCurrentActivity("Expected CardIOActivity (scan)", "CardIOActivity");
            solo.clickOnButton("Keyboard...");
        }

        solo.assertCurrentActivity("Expected DataEntryActivity", "DataEntryActivity");

        solo.enterText(0, "4111111111111111");
        solo.enterText(1, "12/22");

        solo.clickOnButton("Done");

        solo.assertCurrentActivity("Expected completion", MyScanActivity.class);

        assertEquals("Card Number not found", true, solo.searchText("Card Number:"));
        assertEquals("Expiry not found", true, solo.searchText("Expiration Date: 12/2022"));

    }
}Based on https://github.com/card-io/android-scan-demo

Why not Groovy?

What do we need?


The server side



  • blazing fast unit tests
  • dynamic languages in tests
  • dependency injection & mocking
  • examples and specs instead of tests (BDD)
  • integration and functional tests

Tests pyramid

by Mike Cohn

Unit tests


  • testing one unit at a time
  • separates from the environment
  • in memory database (if any)
  • simple
  • fast!


Lets escape from Dalvik VM!

Still JUnit?

JUnit -> Spock

Behavior Driven Development
void testAggregateSevesUser() {
    // given
    User user = new User();

    // when
    aggregate.store(user);

    // then
    assertEquals(
        user,
        aggregate.findOnly()
    );
}
def 'aggregate should save user'(){
    given:
      def user = new User()

    when:
      aggregate.store user

    then:
      aggregate.findOnly() == user
}

JUnit -> Spock

Assertions
// expect
assertNotNull(title);
assertContains(title, "About");
expect:
  title =~ 'About'

JUnit -> Spock

Parametrization
// ...

// then
assertNotNull(activity.getState());
assertNotNull(activity.getStatus());
assertNotNull(activity.getIcon());
assertNotNull(
    activity.getEverythingElse()
    );
// ...

expect:
  activity."$field" != null

where:
  field << [
    "state", "status",
    "icon", "everythingElse"
  ]

JUnit -> Spock

Mocks
// given
User userMock = mock(User.class);
when(userMock.getEmail())
    .thenReturn("email@email.com")
    .thenReturn(null);

// ...

// then
verify(userMock, times(2)).getEmail()
given:
  def userMock = Mock(User)
  userMock.getEmail() >> [
    'email@email.com', null
  ]

// ...

then:
  2 * userMock.getEmail()

JUnit -> Spock

Exception handling - simple
@Test(expect=RuntimeException.class)
public void myTest() {

  thisThrowsSomething();
}
when:
thisThrowsSomething()

then:
thrown(RuntimeException)

JUnit -> Spock

Exception handling - detailed
@Test
public void myTest() {

  try {
    thisThrowsSomething();
    fail();

  } catch(RuntimeException e) {
    assertContains(
      e.getMessage(), 'No such user')
  }
}
when:
  thisThrowsSomething()

then:
  def e = thrown(RuntimeException)
  e.message =~ 'No such user'

JUnit -> Spock

Groovy
then:
    userStorage.getAllUsers().find{it.id == id}?.name == "Szymon"

Condition not satisfied:

userRepository.findAll().find{it.name == 'Szymon'}?.age == 10
|              |         |                          |   |
|              |         null                       |   false
|              [A$User(Piotr, 12)]                  null
A$UserRepository@22d3d11f
<Click to see difference>

So what's the problem, anyway?


A close look into android.jar





 It's a mock!

Wasn't it open source?



  • native code
  • not exactly Java
  • not as portable
  • IDE support


One the other hand - the code is mostly simple

Robolectric

The story about Robots, Classloaders and Stubs

Classloaders

Switch class definition

Implementing classes

in Robolectric

Requirements

vel how to play with it

@RunWith(RobolectricTestRunner.class)
public class MyActivityTest {
    // ..
}

Do we need test runner?


public class MyTest {
  public void test() {
    Activity x = // Activity on the left loaded on MyTest bootstrap
        getMyActivity(); // but the method returned shadowed activity
  }
}
java.lang.ClassCastException: android.app.Activity
  cannot be cast to android.app.Activity
        

Meet RoboSpock


class MyActivitySpec extends RoboSpecification {...}
        

Using both


Robolectric
  • classloader
  • shadows
  • DB support
Spockframework
  • runner
  • extension points
  • everything else


Thank you Open Source!


Robolectric

The MIT License
Developed by (mostly) PivotalLabs

https://github.com/pivotal/robolectric

Thank you Open Source!


Spockframework

Apache 2.0
Developed (mostly) by Peter Niederwieser

https://code.google.com/p/spock/

RoboSpock examples


ORMLite


def "should throw SQL Constraint exception on existing primary key"() {
  given:
    def dao = databaseHelper.getDao(DatabaseObject)

  and: 'stored object'
    def dbObject = new DatabaseObject("test", 4, 1)
    dao.create(dbObject)

  when: 'duplication'
    dao.create(dbObject)

  then:
    def exception = thrown(RuntimeException)
    exception.message =~ "SQLITE_CONSTRAINT"
    exception.cause.class == SQLException
}

RoboGuice

class TaskActivityTestGroovy extends RoboSpecification {

  @Inject WebInterface webInterface

  def setup() {
    inject {
      install(TestTaskExecutorModule)
      bind(WebInterface).toInstance(Mock(WebInterface))
    }
  }

  def "should load text from async task"() {
    given:
      def taskActivity = new TaskActivity()

    when:
      taskActivity.onCreate(null)

    then:
      taskActivity.asyncText.text == "WebText"
  }
}

Custom shadows

@UseShadows(MyActivityManagerShadow)
class TaskActivityTestGroovy extends RoboSpecification {

  def "should load text from async task"() {
    given:
      def taskActivity = new TaskActivity()

    when:
      taskActivity.onCreate(null)

    then:
      taskActivity.asyncText.text == "WebText"
  }
}

Why is it all so important?


Mastering TDD is hard enough

We need stable, functional and powerful tools
to focus on important part - the logic

Roadmap

State of the art


Robolectric 1.2 support
Guice support
Custom shadows

Plans


Updating to robolectric 2.0 (after release)

Tutorial, docs, ...

Help us spreading the word

Q&A