Showing posts with label robolectric. Show all posts
Showing posts with label robolectric. Show all posts

Wednesday, December 1, 2010

Creating a Shadow for Robolectric in 6 steps

Some days ago I asked for a pull request in Robolectric's github repo. I finished my ShadowConnectivityManager. The discussion can be read here. I will explain how I did it so someone else can continue creating Shadows :).

Why do I need a ShadowConnectivityManager?
The app we are doing right now requires internet to work so we did a nice SplashScreen that checks if internet is available.
If there is internet go to the main Activity, else show a cute msg saying that you don't have internet.

So we created a class called ConnectionStatus which has a method:

boolean isOnline();


NOTE: We should have created the test first but since we are also giving a try to roboguice we did the code before the test.


public class ConnectionStatus implements IConnectionStatus {

private ConnectivityManager cm;

@Inject
public ConnectionStatus(ConnectivityManager cm) {
this.cm = cm;
}

public boolean isOnline() {
NetworkInfo netInfo = cm.getActiveNetworkInfo();
if (netInfo != null && netInfo.isConnectedOrConnecting()) {
return true;
}

return false;
}
}


Instead of doing:
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
we can inject it :)

Ok, time to test. When I try to inject a ConnectivityManager to a test there was an exception saying that my variable should have a @Nullable annotation.

Conclusion:
Null instance in test scope == "Robolectric needs your help! Create a Shadow object!"

STEP 1: Understand how Android works
Robolectric basically mocks Android's behavior. You need to understand how it works for you to mock it correctly.

In our case:

* You use an Android's context to get the ConnectivityManager which is a SystemService.
* You ask the ConnectivityManager to give you the ActiveNetworkInfo.
* The class NetworkInfo has a method isConnectedOrConnecting() which will determine if your are online or not.

STEP 2: Decide how the Shadow should be used

We want to be able to set if we are connected or not and check if our ConnectionStatus class is returning things accordingly.
To do that we will leave a flag inside the NetworkInfo's Shadow to let the user decide what it should return.

STEP 3: Code the Shadow
In our case we need to code ShadowConnectivityManager and ShadowNetworkInfo

// With the Implements annotation we tell Robolectric which Android's class this is shadow of.
@Implements(ConnectivityManager.class)
public class ShadowConnectivityManager {

// We will hold here an instance of the ActiveNetworkInfo.
private NetworkInfo networkInfo;


// All methods that exists in the real Android's class and overridden by the shadow
// must have an Implementation annotation. I don't know what for :(
@Implementation
public NetworkInfo getActiveNetworkInfo() {
return networkInfo == null ? networkInfo = newInstanceOf(NetworkInfo.class) : networkInfo;
}
}

Ok, my first big doubt was: "Ok, I need to return a NetworkInfo but I need it to be a shadow, how do I do it?"
That what Robolectric.newInstanceOf() is for :)



@Implements(NetworkInfo.class)
public class ShadowNetworkInfo {

private boolean isConnected = true;

@Implementation
public boolean isConnectedOrConnecting() {
return isConnected;
}

/**
* Non-Android accessor
* Sets up the return value of {@link #isConnectedOrConnecting()}.
*
* @param isConnected the value that {@link #isConnectedOrConnecting()} will return.
*/
public void setConnectionStatus(boolean isConnected) {
this.isConnected = isConnected;
}
}


Pretty easy as well. We have an Non-Android accesor that we will use to set the wanted connectivity status.


STEP 4: Modify Robolectric.java

* Add your Shadow classes to the list in the getDefaultShadowClasses() method.
* Create the shadowOf methods:

public static ShadowNetworkInfo shadowOf(NetworkInfo instance) {
return (ShadowNetworkInfo) shadowOf_(instance);
}

public static ShadowConnectivityManager shadowOf(ConnectivityManager instance) {
return (ShadowConnectivityManager) shadowOf_(instance);
}

* Optional: I needed to modify ShadowApplication as well.

Why?
Application is in charge of the getSystemService's method.
We need to modify it to return our ShadowConnectivityManager just adding this:

if (name.equals(Context.CONNECTIVITY_SERVICE)) {
return connectivityManager == null ? connectivityManager = newInstanceOf(ConnectivityManager.class) : connectivityManager;
}



STEP 5: Test your Shadow
I will just copy and paste the test we did. It's quite simple:

@RunWith(WithTestDefaultsRunner.class)
public class ConnectivityManagerTest {
private ConnectivityManager connectivityManager;
private ShadowNetworkInfo networkInfo;

@Before
public void setUp() throws Exception {
Robolectric.bindDefaultShadowClasses();
Robolectric.application = new Application();
connectivityManager = (ConnectivityManager) Robolectric.application.getSystemService(Context.CONNECTIVITY_SERVICE);
networkInfo = Robolectric.shadowOf(connectivityManager.getActiveNetworkInfo());
}

@Test
public void getConnectivityManagerShouldNotBeNull() {
assertNotNull(connectivityManager);
assertNotNull(connectivityManager.getActiveNetworkInfo());
}

@Test
public void networkInfoShouldReturnTrueCorrectly() {
networkInfo.setConnectionStatus(true);

assertTrue(connectivityManager.getActiveNetworkInfo().isConnectedOrConnecting());
}

@Test
public void networkInfoShouldReturnFalseCorrectly() {
networkInfo.setConnectionStatus(false);

assertFalse(connectivityManager.getActiveNetworkInfo().isConnectedOrConnecting());
}
}


STEP 6: Share it!
Ask for a pull request!


How did our testcase end up being?


@RunWith(XTestRunner.class)
public class ConnectionStatusTest {

@Inject
private ConnectivityManager cm;

@Test
public void connectionStatusShouldBeFalseWhenOffline() {
ConnectionStatus connectStatus = new ConnectionStatus(
getConnectivityManager(false));
assertFalse(connectStatus.isOnline());
}

@Test
public void connectionStatusShouldBeTrueWhenOnline() {

ConnectionStatus connectStatus = new ConnectionStatus(
getConnectivityManager(true));
assertTrue(connectStatus.isOnline());

}

private ConnectivityManager getConnectivityManager(boolean connected) {

ShadowNetworkInfo ni = Robolectric.shadowOf(cm.getActiveNetworkInfo());
ni.setConnectionStatus(connected);

return cm;
}
}



Conclusion:

While I code this simple Shadow for ConnectiviyManager someone else created a Shadow for AsyncTask and SQLite!
Shadows amount is increasing version by version => Android TDD is getting easier and easier thanks to Robolectric.

Robolectric it's a very cool fw. I consider myself a newbie to it but I could place some commits to it ;)
The support in the mailing list is excellent and I guess in the next release we will be able to add mvn support completely.

What are you waiting to give it a try?

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!