Testing your RxSwift code, part 1

I sat down recently and learned the basics of writing unit tests for RxSwift. It was way easier than I expected and that’s why I’d like to show few of the tests I got to write.

Before I start on the code itself I just want to mention how great contributing to open source is. If I didn’t jump in to helping around on the RxSwift-Ext project I’d probably haven’t looked into writing unit tests with RxSwift yet, but I did - and that’s great.

So let’s have a look at some of the unit tests I wrote…

Intro to RxTests

RxTests is a separate library (available as well through CocoaPods), which you should import in your test target to be able to use some really handy classes to write your tests.

Here’s how the Swift-Ext Podfile test target looks like:

target 'RxSwiftExtDemoTests' do
	pod 'RxSwift+Ext'
	pod 'RxSwift'
	pod 'RxTests'
end

RxTests gives you few handy tools like:

  • TestableObserver<ElementType> - an observer, which records all emitted events so you can inspect them and run your asserts on those events,
  • TestScheduler - a scheduler which let’s you control values and time, and let’s you create testable observers,
  • == (lhs: Event<Element>, rhs: Event<Element>) adds Equatable implementation to Rx events so you can easily check recorded events.

Let’s have a look how to use those!

Simple tests for an rx operator

In Custom convenience operators with RxSwift, Part 2 I discussed creating the unwrap() operator, which unwraps non-nil values emitted by an observable.

It took me a lot of time to make that operator work so when I saw folks contribute their operators to RxSwift-Ext I naturally also wanted to merge mine in.

In order to do that though, I wanted to add unit tests first…

Drafting a unit test class

Here’s how the setup of the unit tests file for unwrap() looked like:

import XCTest

import RxSwift
import RxSwift_Ext
import RxTests

class UnwrapTests: XCTestCase {
    private var observer: TestableObserver<Int>!
    let numbers: Array<Int?> = [1, nil, Int?(3), 4]
    
}

The class itself is a normal XCTestCase test but it features a TestableObserver. You have to specify what kind of values it will observe so that you can have compile time checks if your Observable is emitting the data type you expect to have.

For my tests I chose to test with an array of Int? values since unwrap() takes in a Type? and returns Type.

The numbers array is the sequence of values I’d use to feed into unwrap() - it includes integers, a nil value, and an Optional integer number.

setUp()

Next I needed to add a setUp() method in the unit test class to prepare everything before the actual test methods run.

override func setUp() {
    super.setUp()
        
    let scheduler = TestScheduler(initialClock: 0)
    observer = scheduler.createObserver(Int)
}

At this point I dug multiple times through the RxExample app included in the RxSwift repo in order to figure out how to use a test scheduler.

First of all my tests didn’t need to happen asynchronously so I didn’t have to use virtual time for my test scheduler. I set the initial clock at time 0 and didn’t bother with time any further.

After creating a test scheduler I created a test observer by calling TestScheduler.createObserver(Type). Type is the type of values I expected the observer to capture from my Observable.

At this point my code was ready to emit some values, I added:

numbers.toObservable()
     .unwrap()
     .subscribe(observer)

That code turned the initial array I had to an observable, fed it through unwrap(), and finally sent it off to the test observer.

The final line I added to setUp() was:

scheduler.start()

That would make the scheduler start running, have the observer consume all values from the sequence, and wrap-up.

Simple checks on the recorded events

I decided to start easy by checking if the list of recorded events matched some general expectations.

First I wanted to check if unwrap() filtered all nil elements:

func testUnwrapFilterNil() {
    XCTAssertFalse(observer.events.contains {event in
        event.value == nil
    })
}

The code just checks if none of the recorded events in observer.events contains a nil value. Done!

Ok, what next? I wanted to see if the number of the output values is the one I expect: the number of the input values minus the amount of nil values plus the Complete event. I just added it to the same test method:

XCTAssertEqual(
    observer.events.count,
    numbers.count - 1/* the nr. of nil elements*/ + 1 /* complete event*/
)

Also done! Sweet.

Check the recorded events

TestObserver not only records the events that your Observable emits but also the values they carried and the time marks they happened at.

It’s really easy to check if the events you expected were the ones recorded. Let’s have a look at what I did (again, how to do all of this I found in the RxSwift repo sources):

func testUnwrapResultValues() {
    //test elements values and type
    let correctValues = [
        next(0, 1),
        next(0, 3),
        next(0, 4),
        completed(0)
    ]
}    

First I defined an array of expected events - 3 .Next events all happening at virtual time 0 and .Completed event emitted when the input values are over.

Now I just needed to compare the correctValues array to the list of events TestObserver has recorded:

XCTAssertEqual(observer.events, correctValues)

And that’s a wrap!

Conclusion

It was easy and quite simple to write unit tests for unwrap() because I could run all tests synchronously over the values of my input array. But TestObserver and TestScheduler allow for more complex unit tests as well. In part 2 I’ll look into writing simple asynchronous unit tests with RxSwift. Till then!

If you want to have a look at the finished unit test class for unwrap() you can find it here: UnwrapTests.swift

I hope this post has been useful! Do you know a better way to do any of this? Seen a bug? Ping me on Twitter at icanzilb

Hope that post was helpful, and if you want to get in touch you can find me here

Share this post:


If you'd like to learn how to create professional production apps with RxSwift, the best resource out there is the RxSwift book written by Florent Pillet, Junior Bontognali, Marin Todorov, & Scott Gardner.

It features 20+ chapters covering the basics, the Rx operators, and advanced topics like testing, error handling, and app architecture.

Available from Ray Wenderlich: » Learn more.