Easily test your one-off async operations on Android with Dagger2 and RxJava

This demo application utilizes RxJava, Dagger2, and JUnit. Explaining these libraries is out of scope for this article, but the internet has plenty of content on those topics, so check them out before continuing on. This also assumes you are familiar with the basics of JUnit testing.

The Problem

Asynchronous methods can be great for the UX, but are harder to test.

RxJava makes threading easy, but sometimes you do not want the overhead of subscribing to an rx.Observable. Maybe you want to call a simple void async method.

public void saveFavoriteColorAsync(String favoriteColor)

Let’s save our favorite color

/**
* A repository to store someone’s favorite color.
*/

public interface FavoriteColorRepo {
/**
* Loads the user’s favorite color.
*
@return ex- “blue”
*/

public Observable<String> getFavoriteColor();
/**
* Will asynchronously save the users favorite color.
*/

public void saveFavoriteColorAsync(String favoriteColor);
}

I call my data access classes “repos” — short for repository. Now let’s look at an implementation of this method.

@Override
public void saveFavoriteColorAsync(final String color) {
Observable.just(color)
.map(s -> {
simulateLongRunningOperation();
favoriteColor.set(s); //"saves" the favorite color
return null; //nothing needs to be returned here
})
.subscribeOn(Schedulers.io())
.subscribe();
}

private void simulateLongRunningOperation() {
try {
//Let's pretend it took a while for us to save our favorite color.
Thread.sleep(1000L);
} catch (InterruptedException e) {
Exceptions.propagate(e);
}
}

Notice that calling saveFavoriteColorAsync() will kick off a new thread that will take a second to complete.

The Test

There are four lines of code in this test method.

@Test
public void testSaveFavoriteColorAsync() throws Exception {
final String expected = "green"; //1

favoriteColorRepoImpl.saveFavoriteColorAsync(expected); //2

final String actual = favoriteColorRepoImpl.getFavoriteColor()
.toBlocking().first(); //3

Assert.assertThat(
"Favorite color should now be green after saving",
actual, Matchers.equalTo(expected)); //4
}

This test is going to fail. Why? Line #2 kicks off an async operation that will take 1 second to complete. Line #3+4 will execute BEFORE line #2’s async operation will finish. In order for this test to pass, we need a way to ensure that line #2 will complete before line #3 is executed.

So how do you test the saveFavoriteColorAsync() method? You may have heard singletons are bad for testing — this is why. Using a hardcoded singleton, Schedulers.io(), prevents us from specifying the threading during our test. Fear not— we can use Dagger2 to inject our schedulers and create a ThreadingModule!

Control your rx.Schedulers with Dagger2

/**
* A dagger module to decouple threading even further!
*/
@Module
public class ThreadingModule {
public static final String IO_THREAD = "io";

@Provides
@Named(IO_THREAD)
@Singleton
public Scheduler provideIoScheduler() {
return Schedulers.io();
}
}

Now, instead of calling Schedulers.io() in our async save operation, we inject the dependency in the constructor of our data access class.

public FavoriteColorRepoImpl(Scheduler io) {
this.io = io;
}

This is the first step to allow us to swap out the IO scheduler for the MainThread scheduler during test time. What’s next?

Use a Dagger2 mock module during testing

/**
* A mock threading module where all schedulers run on the main thread.
*/
@Module
public class MockThreadingModule extends ThreadingModule {

@Override
public Scheduler provideIoScheduler() {
return AndroidSchedulers.mainThread();
}
}

Now the main thread scheduler can be provided instead of the default IO scheduler. We can do this in our JUnit test’s setUp() method.

note: a base static component is stored in our Application class. This is our entry point for swapping out dependencies during test time.

@Before
public void setUp() throws Exception {
//let's set a new component for our application
final ApplicationComponent applicationComponent =
DaggerApplicationComponent.builder()
.applicationModule(
new ApplicationModule(
(MyApplication) RuntimeEnvironment.application
)
)
//and supply a different threading module.
.threadingModule(new MockThreadingModule())

.build();

MyApplication.setComponent(applicationComponent);

favoriteColorRepoImpl = (FavoriteColorRepoImpl) MyApplication.getComponent().getFavoriteColorRepo();
}

Now when your run the test, it passes!

Resources

A gist containing just the important classes.

The full source on GitHub.

Let me know if you have any questions or suggestions to improve this (send a pull request)!