Few
articles earlier
I showed you how you can wrap presentation of
UIAlertController
with the Observable. Despite there weren’t any tests in the sample project,
I’ve written the whole sample in TDD
. I didn’t upload test files for the last time because I didn’t want to overload you 😃.
No matter how much RxSwift simplifies writing a code you should
always write unit-tests
. The feedback which tests provide is huge for the business and even for you because tests help you in revealing bad code smells in your architecture.
In this article, I want to show you all the tests I’ve written and how you can test the Observables with RxTest. Enjoy reading 📚💪
Recap
In the previous article
you built the project which allows you to use the last image taken or to pick the image from the gallery. The user had to choose the source of the image from the actionSheet displayed by the
UIAlertController
. All the presentation was performed by using RxSwift & Observables.
Before we start writing unit tests, I need to say that I’ve changed how the
AvatarViewModel
looks like. I simplified the viewModel and I added one
PublishSubject<Void>
to the viewModel which represents the button taps. You can find the current version of the view model
here
.
Testing the AvatarViewModel
As you can see, the
AvatarViewModel
uses
ImageHaving
to receive an image. This is the first thing which could be tested. Since
ImageHaving
is a protocol you can, and you should 😏, create a stub to
fake the behavior
of that dependency. You want to test just the
viewModel
, not all the objects connected with it:
The stub is pretty simple. Usually, stubs have few exposed properties which make it possible to fake the behavior of the dependency.
Now you can initialize the
AvatarViewModel
in the test file:
No test should depend on the other. I recommend to always reinitialize the
rx_disposeBag
in the
setUp
. Creating new dispose bag will dealloc the old one which will dispose all the subscriptions made before.
Straightforward test with the PublishSubject
Your first test will test if
AvatarViewModel
returns the
UImage
in the
image: Driver<UIImage>
on button press:
As the first step, you have to bind the input to the view model. You need something which imitates touches at the button. The easiest solution is to use the
PublishSubject<Void>
:
When you have the input, it is the time to configure the output:
At the end you need to fake the button tap and then compare the output with expected result:
The whole test looks like this:
Better tests with RxTest & TestScheduler
Using
PublishSubject<Void>
is a straightforward solution. You just create the subject and invoke button taps by calling
onNext(())
.
For this example,
PublishSubject
is enough. However, sometimes you would like to see how the object behaves when it receives multiple events.
ReactiveX
offers you another framework called
RxTest
. You can find the
TestScheduler
in it which can help you in writing tests for Observables.
The usage of TestScheduler
In most cases, you are going to use 2 methods of
TestScheduler
, which are
createObserver
and
createHotObservable
.
createObserver
allows you to create the
TestableObserver
which records every event send to it.
createHotObservable
creates 🔥Observable, where you can pass what events should it send at given schedule.
Let’s use it in your test method. First of all, you have to create the scheduler:
initialClock? o.O What is that?
You may ask what is the
initialClock
in the init. Schedulers are used for dispatching work across many threads. They are the heart of asynchronous nature of Observables. However, testing asynchronous code is not an easy thing to do.
TestScheduler
dispatches its work to the main thread and uses the virtual time to record the moment when an event happen. In most cases, you will set the
initialClock
as 0.
TestableObserver & TestableObservable
When you have the
TestScheduler
you can go further. To replace PublishSubject from the previous test sample you can use
createHotObservable
function:
You probably are thinking what is the
next(100, ())
.
When you use
createHotObservable
method, you have to provide what events the Observable will send at a particular time. The first argument in
next(100, ())
says the event will be sent at
the time 100
. The second argument is what will be sent. In our case, it is a void (button doesn’t send anything more than just a message it was tapped).
Besides replacing the subject you can use observer from the testScheduler to subscribe for the viewModel’s output:
Now, when the input and output is configured properly you can add the assertion into test… and fire the testScheduler before 😄:
The whole test method looks like this:
Small refactor & improvements
You should treat your tests like the production code :). To keep tests more readable I usually create a helper function to get rid of duplicated code, even in unit tests. I’ve found it readable to replace explicit binding with just a call of
simulateTaps(at: 100, 200)
:
Tests & the Driver unit
Testing the
Driver
can be tricky.
Driver
always switches the job into the MainScheduler. As a result, the testScheduler may not catch any event and your tests won’t ever pass. Thankfully the problem doesn’t affect the test above.
However, the solution for the problem is pretty simple, so I think it is worth mentioning in the article about unit-tests for RxSwift 🙂
RxCocoa has a
func driveOnScheduler(_ scheduler: SchedulerType, action: () -> ())
. It allows you to change the scheduler for every
Driver
created in the
action
closure. In our case it would be a matter of wrapping the init of the viewModel:
RxBlocking
Rx offers you yet another way to tests a reactive code.
RxBlocking
is a separate library which should be used only in test target. It subscribes for a given
Observable
and blocks the thread to get the result synchronously. I used
RxBlocking
in one test method for
GalleryReader
:
If you use
Nimble
the test can become even shorter by using
RxNimble
matchers:
Drawbacks of using RxBlocking
Using
RxBlocking
&
RxNimble
may seem to be an attractive solution. However, I’ve found it is a good practice to check if the Observable returns also the
completed
event. When you use
toBlocking().first()!
you check only the first event which comes from the sequence.
Another downside is
toBlocking()
doesn’t use any timeout. Sometimes, you can have a test which never finishes. This is somehow against what tests should give you. Tests should offer you a
quick
feedback what part of the code is broken.
Check all the tests inside the project. I’ve only shown you tests for the ViewModel and one for the GalleryReader. Every class which contains some business logic was covered with unit-tests 🙂
When you use
RxTest
&
TestScheduler
remember about:
invoking
start()
before making an assertion. Sometimes I forget about it and I waste the time for a trivial mistake ¯_(ツ)_/¯
using
driveOnScheduler
when your output is the
Driver
I also recommend you the
5th chapter
of RxSwift Primer. It is also about unit-tests and Rx code.
If you liked the article help me to reach for more people. You can share the article by pressing the buttons below. Cheers!
References:
Title image –
flickr.com
– Sanofi Pasteur CC BY-NC-ND 2.0
Reactive programming is awesome and really makes complex I/O stuff simple. However, FRP is totally different approach how you look at the events and programming. Such a change in thinking requires good quality materials which can make the learning process simpler and quicker. Here’s the list of materials
Adam Borek
I’ve noticed a lot of beginners in RxSwift ask about DisposeBag. DisposeBag isn’t a standard thing in iOS development neither in other Rx’s implementations. Basically, it is how RxSwift handles memory management on iOS platform.
In this article, I want to answer for few question like what
Adam Borek
Recently somebody asked me how he can send a default value if the stream hasn’t sent a new value in 5 minutes using RxSwift.
The final solution is short, however it uses few operators like merge, concat & flatMapLatest together which makes it more … advanced usage of Rx.
Adam Borek