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 📚💪
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
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:
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
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
PublishSubject<Void> is a straightforward solution. You just create the subject and invoke button taps by calling
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 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
You probably are thinking what is the
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
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:
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:
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.
Summary & Where to go from here?
Download the complete sample project here.
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:
start()before making an assertion. Sometimes I forget about it and I waste the time for a trivial mistake ¯_(ツ)_/¯
driveOnSchedulerwhen your output is the
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!
Title image – flickr.com – Sanofi Pasteur CC BY-NC-ND 2.0