Within test modules, RxJava gives you access to a happy little class called the TestObserver
. That’s what this is all about.
We can get a TestObserver by calling test()
on any observable. What this does is it executes your observable as if you subscribed to it, and it does so on the thread your test is running on.
@Test fun givenValidResponse_whenGetPost_thenMappedPostReturned() { given(placeholderApi.getPost(postId)).willReturn(Single.just(post)) given(placeholderMapper.mapPost(post)).willReturn(mappedPost) val postTestObserver = placeHolderService.getPost(postId) postTestObserver.test() .assertComplete() .assertNoErrors() .assertValue { it == mappedPost } }
TestObserver
then gives you access to plenty of useful methods allowing to assert your stream and values in the ways you see fit. There are a lot of these different assertions so your tests can be as comprehensive as you want them to be.
Some of these assertions include:
assertComplete – asserts that the subscriber received exactly one onComplete
event
assertValue (and all its variants) – asserts that the subscriber received a value matching the given predicate
assertError – asserts that the subscriber received a specific error instance / class
assertNever – asserts that the subscriber never received a value matching the given predicate
These are only a few of the many assertions you are provided with.
Await
You might want to test observable streams that are time-based, based on a multiple number of emissions, or infinite timer kind of things. In which case, the await
will help you out.
fun getTimerCounting() = Observable.interval(1000, TimeUnit.MILLISECONDS) @Test fun whenTimerIsCounting_thenNumberOfEmissionsReturned() { val testObserver = getTimerCounting().test() testObserver.await(5000, MILLISECONDS) testObserver.assertValues(0, 1, 2, 3, 4) }
In a real scenario, these 0, 1, 2, 3, 4
values would emit one each second, so they would take 5 seconds to fully emit. In this test scenario, thanks to await
, those 5 seconds happen instantly.
So your tests that test things that take time don’t need to take time when testing… Just pretend for a second that makes sense, alright? Good. Moving on.
How NOT to test RxJava: BlockingGet
I’ve seen this a couple times so I thought it’s worth a mention.
blockingGet
is another function you can call on observables to get their value.
The above test can be restructured like this.
@Test fun givenValidResponse_whenGetPost_thenMappedPostReturned() { given(placeholderApi.getPost(postId)).willReturn(Single.just(post)) given(placeholderMapper.mapPost(post)).willReturn(mappedPost) val post = placeHolderService.getPost(postId).blockingGet() assertThat(post).isEqualTo(mappedPost) }
Looks pretty nice doesn’t it? We get the single value that the Single would emit, then we apply our assertions to it.
Oh my dear Freddy, you are so naive.
Using blockingGet
in unit tests is not the grave sin it is if it were used in production code, but it still is a sin for the fact that you are missing out on assertions you could be making with TestObservable
.
You could be doing asserts which revolve around the lifecycle of the observable, onNext
, onComplete
, onError
and assert more than just the emitted value.
And when it comes to situations where await
is needed, i.e. time-based events, blockingGet
would make the test actually have to wait those 5 seconds or whatever instead of passing that time instantly.
Post Summary
- Use
test
to transform your observables intoTestObserver
- Use its various assert methods to test the lifecycle and values of its emissions
- Use
await
to instantly test time-based observable functions blockingGet
is bad
Happy coding ༼ つ ◕_◕ ༽つ