RxTest – How to test the Observable in Swift

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.

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:

  • 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

You Might Also Like

  • Ryan

    Using RxBlocking, how would you check the observable for the completed event?

    • Hey Ryan,

      I think the only possibility is to use last() or toArray() instead of the first() after toBlocking(). Documentations say that:

      Blocks current thread until sequence terminates. If sequence terminates with error, terminating error will be thrown.”

      • Ryan

        Cool. So no explicit testing for completed, but by using one of those operators (or single(), I guess), it can be implied that the sequence completed.

  • Ayoub Khayati

    RxBlocking has timeout -> toBlocking(timeout: 30) I think it needs to be removed from the Drawbacks.

    • Hi Ayoub,
      Yes indeed toBlocking has possibility to set the timeout but it isn’t set by default. I’ll change that sentence to be more understandable πŸ˜‰

      However, I still think this is a drawback of RxBlocking which you need to be aware of πŸ˜‰

      PS Although RxBlocking has few drawbacks I still use it in places where it fits πŸ˜‰