Sunday, November 28, 2010

Robolectric with Roboguice in the TDD mix

My last blog post was about a little skeleton I did with every piece of fw I needed to start a new Android application using TDD. Phil Goodwin post a comment saying I should try Robolectric. So here's my blog post about it :)

Robolectric is a unit test framework that de-fangs the Android SDK jar so you can test-drive the development of your Android app. Tests run inside the JVM on your workstation in seconds.


So before adding it to my skeleton I did a little test. In one of my projects I have an util class that reads some cars from a json file located at /res/raw. You can see how it went in this mail list thread.

Ok, next step to place Robolectric inside my skeleton was to mavenize it.
I opened an issue, created a new mail list thread and started working on it. Fortunately Christian Williams was interested and we started hacking the pom together.

At the time I am writing this the mvn support is working and we are missing the upload to mvn central. I guess next release of Robolectric will have the jar available through mvn central.

For the test project I was using robotium and Android-mock. I will remove them since Robolectric is ready to join my test project's pom as a dependency :)

Shorts steps to install robolectric in your mvn repo.

* Clone https://github.com/mosabua/maven-android-sdk-deployer
* mvn clean install
* Clone https://github.com/pivotal/robolectric/tree/master
* mvn clean install

My test-app's pom.xml looks like this. Finally tests don't need an android emulator to run.

My new project uses Roboguice and the first Activity we made is a SplashScreen. The layout is just a RelativeLayout with a background set, an ImageView with the app's logo in the center of the screen and a progressBar. The Activity's code looks like this:

public class SplashActivity extends GuiceActivity implements ISplashView {

@InjectView(R.id.splash_progress_bar)
private ProgressBar mProgressBar;

@Inject
private SplashPresenter mPresenter;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPresenter.setView(this);
setContentView(R.layout.splash);
}
....
}



So our first test in the application was the simple shouldHaveALogo() mentioned in the robolectric's user guide. We struggle a little with @mgv making Roboguice with Robolectric work together but we did it. This is how:

To make it work, we needed to extend RobolectricTestRunner.

public class XTestRunner extends RobolectricTestRunner {

public XTestRunner(Class testClass) throws InitializationError {
super(testClass, "../X-app/AndroidManifest.xml",
"../X-app/res");
}

@Override
protected Application createApplication() {
return new IoCApplication();
}
}


IoCApplication is just the class that extends GuiceApplication. Yep, the one that overrides addApplicationModules.

The test ends being:

@RunWith(XTestRunner.class)
public class SplashActivityTest {

private SplashActivity splashActivity;

@Before
public void setUp() throws Exception {
splashActivity = new SplashActivity();
splashActivity.onCreate(null);
}

@Test
public void shouldHaveLoadingLogo() throws Exception {
ImageView logo = (ImageView) splashActivity.findViewById(R.id.splash_logo);
ShadowImageView shadowLogo = Robolectric.shadowOf(logo);
assertThat(shadowLogo.getResourceId(), equalTo(R.drawable.loading_logo));
}
}



The constructor is used to point Robolectric to our app project's AndroidManifest and res folder. Nothing hard there.

The magic comes in the overriden method createApplication(). The parent class use it to set the application context to Robolectric.java class in this method:

public void setupApplicationState(String projectRoot, String resourceDir) {
ResourceLoader resourceLoader = createResourceLoader(projectRoot, resourceDir);

Robolectric.bindDefaultShadowClasses();
Robolectric.resetStaticState();
Robolectric.application = ShadowApplication.bind(createApplication(), resourceLoader);
}



What do we need the application for?
Because it's used in GuiceActivity. If you check the onCreate() method, it does this:

@Override
protected void onCreate(Bundle savedInstanceState) {
final Injector injector = getInjector();
scope = injector.getInstance(ContextScope.class);
scope.enter(this);
injector.injectMembers(this);
super.onCreate(savedInstanceState);
}


getInjector()? Where's does it came from?

public Injector getInjector() {
return ((GuiceApplication) getApplication()).getInjector();
}


Conclusion: Every injection with Roboguice uses GuiceApplication.
Robolectric looks like a very powerful test fw. We will definitely use it for this new project we are working on.

Next step: ConnectivityManager Shadow!

2 comments:

  1. Thanks for sharing -- good stuff! What's your opinion on Roboguice? How much of a performance impact does it have on a typical app?

    ReplyDelete
  2. @Matthias: I didn't use it yet, this is my first project with it. I guess I will be able to answer that when this project finishes :)

    Thanks!

    ReplyDelete