Testing Your RxSwift Code

In this tutorial, you’ll learn the key to testing RxSwift code. Specifically, you’ll learn how to create unit tests for your Observable streams. By Shai Mishali.

5 (17) · 1 Review

Download materials
Save for later
Share

Writing reactive apps with RxSwift is a conceptually different task than writing apps “the regular way.” It’s different in the sense that things in your app won’t usually have a singular value but are, instead, represented as a stream of values over the axis of time, known within the RxSwift library as an Observable. This tutorial teaches you the key to testing RxSwift code.

Streams are a powerful mechanism that let you, as a developer, react to changes to ensure that your app is updated at all times. As much of an advantage as this provides, testing streams of values is not as trivial as simply asserting a single value. But worry not — this tutorial will set you on your way to becoming an RxSwift-testing expert!

This tutorial will teach you how to create unit tests for your Observable streams. You’ll learn some of the available techniques for testing your RxSwift code, as well as some tips and tricks. Let’s get started.

If you’re interested in learning more about building reactive apps with RxSwift, you might want to look into our book: RxSwift: Reactive Programming with Swift..

Note: This tutorial assumes that you are already knowledgeable with how to use RxSwift, as well as how to write basic tests using XCTest.

If you’re interested in learning more about building reactive apps with RxSwift, you might want to look into our book: RxSwift: Reactive Programming with Swift..

Getting Started

Since reactive apps really shine when dealing with changing content, of course you’ll deal with testing an app of that nature!

Use the Download Materials button at the top or bottom of this tutorial. You’ll find the starter project for this tutorial: Raytronome, a fun metronome app you could use to practice your musical accuracy. As you can imagine, since metronomes deal with time, you’ll find a lot of interesting pieces of logic and information to test here.

Open Raytronome.xcworkspace. Then open Main.storyboard. You’ll see it’s a very simple app with only a single screen.

Build and run the app. Tap the Play button to start the metronome. You can also change the time signature and tempo.

Basic Raytronome app

The app consists of a single view controller — MetronomeViewController.swift — and MetronomeViewModel.swift contains all the business logic, which is what you’ll write tests for.

The Challenges of Testing Streams

Here’s a quick recap of the basics of RxSwift and Observable streams.

Working with streams is inherently different from working with basic values and objects; thus, the task of testing them is different, as well.

Values are single and independent; they don’t have any representation or concept of time. Observable streams, on the other hand, emit elements (e.g. values) over time.

Values vs Observable Streams

This means that, when testing streams of values, you’ll often need to test that either:

  • Some stream emits specific elements, regardless of time.
  • Some stream emits specific elements, at specific times. In this case, you’ll need a way to “record” these emitted elements along with when the stream emitted them.

Determining What to Test

It’s usually a good idea to take a few moments to think about what you actually want to test.

As mentioned earlier, you’ll test MetronomeViewModel, the view model containing the actual business logic related to your metronome.

Open MetronomeViewModel.swift. Looking into the view model, you can see outputs responsible for several pieces of logic: the numerator, denominator, signature and tempo strings, the numerator’s actual value, the maximum value for the numerator, as well as streams responsible for the beat.

The app uses instances of Driver to represent all outputs. A Driver is a kind of stream which makes your life easier when dealing with UI components.

MetronomeViewModel's Inputs and Outputs

Let’s think about what you would want to test in the UI. Make a quick list; you want to test that:

  • The numerator and denominator start at 4 and 4.
  • The signature starts at 4/4.
  • The tempo starts at 120.
  • Tapping the Play/Pause button changes the isPlaying state of the metronome.
  • Modifying the numerator, denominator or tempo emits proper textual representations.
  • The beat is “beating” according to time signature.
  • The beat alternates between .even and .odd — the app uses this to set the metronome image at the top of the view.

When writing your tests, you’ll use two additional frameworks bundled with RxSwift, called RxBlocking and RxTest. Each offers different capabilities and concepts for testing your streams. These frameworks are already part of your starter project.

Using RxBlocking

The starter project includes a bare-bones test target with a RaytronomeTests.swift file.

Open it and look around; it imports RxSwift, RxCocoa, RxTest and RxBlocking, and it includes a viewModel property and a basic setUp() method to create a new instance of our view model, MetronomeViewModel before every test case.

Your first test cases will be about making sure the numerator and denominator both start with a value of 4. Meaning, you’ll only care about the first emitted value of each of these streams. Sounds like a perfect job for RxBlocking!

RxBlocking is one of the two testing frameworks available with RxSwift, and it follows a simple concept: It lets you convert your Observable stream to a BlockingObservable, a special observable that blocks the current thread, waiting for specific terms dictated by its operators.

Blocking Observable Stream

It proves useful for situations in which you’re dealing with a terminating sequence — meaning, one that emits a completed or error event — or aiming to test a finite number of events.

RxBlocking provides several operators, with the most useful ones being:

  • toArray(): Wait for the sequence to terminate and return all results as an array.
  • first(): Wait for the first element and return it.
  • last(): Wait for the sequence to terminate and return the last item emitted.

Looking through these operators, first() is the one that is most suitable for this specific case.

Add the following two test cases to the RaytronomeTests class:

func testNumeratorStartsAt4() throws {
  XCTAssertEqual(try viewModel.numeratorText.toBlocking().first(), "4")
  XCTAssertEqual(try viewModel.numeratorValue.toBlocking().first(), 4)
}

func testDenominatorStartsAt4() throws {
  XCTAssertEqual(try viewModel.denominatorText.toBlocking().first(), "4")
}

You use toBlocking() to convert your regular stream to a BlockingObservable and then use first() to wait for and return the first emitted element. You can then assert against it, like you would on any other regular test.

Notice that the test methods include throws in their signatures, since RxBlocking’s operators may throw. Annotating the test method itself with throws is useful for avoiding try! and for gracefully failing the test if it throws an exception internally.

Press Command-U to run your tests.

Testing RxSwift code

As a quick challenge, try and write the next two tests to verify that the signatureText starts as 4/4, while tempoText starts as 120 BPM. The tests should be almost identical to the two above.

Once you’re done, run your entire test suite again to make sure that you’re good to go with four passing tests.

If you get stuck, feel free to peek at the solution by tapping the Reveal button:

[spoiler title=”Tests for Signature and Tempo”]

func testSignatureStartsAt4By4() throws {
  XCTAssertEqual(try viewModel.signatureText.toBlocking().first(), "4/4")
}

func testTempoStartsAt120() throws {
  XCTAssertEqual(try viewModel.tempoText.toBlocking().first(), "120 BPM")
}

[/spoiler]