NimbleRxTest 1.0.0

NimbleRxTest 1.0.0

Maintained by Balázs Hajagos.



 
Depends on:
Nimble>= 0
Quick>= 0
RxSwift>= 0
RxTest>= 0
 

  • By
  • Balázs Hajagos

Nimble RxTests

This tiny helper library helps connecting one of the most popular testing library for swift (Quick) with the reactive world. First when I had to write tests for a reactive project I was struggling with RxBlocking or event unwrapping usually using .debug() to help understanding what is exactly happening in a reactive chain. After a while I thought "Oh, come on! I have this lovely Nimble thingy and I know XCAssert helper things was created for RxTest, why not do the same in Nimble?". Before I realised I used the same code to help me with binding Nimble and Rx together in 3 or 4 projects copying the same files in every one of them.

And so the NimbleRxTest lib was born.

Use case

Chances are if you are using Nimble and Quick/Nimble you have stumbled upon something like this:

// Swift

import Quick
import Nimble
import RxSwift
import RxTest

protocol Alive {}
    
struct Fish: Alive {}
struct Shark: Alive {}
struct Boat {}
    
final class Dolphin {
    var soundEmissions: Observable<String> {
        return encounter.flatMapLatest { thing -> Observable<String> in
            switch thing {
            case is Fish:
                return .just("click")
            case is Alive:
                return .just("whistle")
            default:
                return .empty()
            }
        }
    }
    
    var encounter = PublishSubject<Any>()
}

final class FactionSelectionViewModelTests: QuickSpec {
    override func spec() {
        
        describe("a reactive 🐬") {
            var dolphin: Dolphin!
            var testScheduler: TestScheduler!
            var disposeBag: DisposeBag!
            
            beforeEach {
                dolphin = Dolphin()
                testScheduler = TestScheduler(initialClock: 0)
                disposeBag = DisposeBag()
            }
            
            describe("encountering") {
                var encounters: [Recorded<Event<Any>>]!
                
                context("with a series of things") {
                    beforeEach {
                        encounters = [
                            .next(100, Boat()),
                            .next(200, Fish()),
                            .next(300, Fish()),
                            .next(400, Shark()),
                            .next(500, Boat()),
                            .completed(600)
                        ]
                    }
                    
                    it("emits sounds accordingly") {
                        let observer = testScheduler.createObserver(String.self)
                        
                        testScheduler
                            .createHotObservable(encounters)
                            .bind(to: dolphin.encounter)
                            .disposed(by: disposeBag)
                        
                        dolphin
                            .soundEmissions
                            .bind(to: observer)
                            .disposed(by: disposeBag)
                        
                        // Yeah let's just expect these sounds
                        let expected = ["click", "click", "whistle"]
                        
                        testScheduler.start()
                        
                        // Uhh, let's just assume checking the returned strings is enough.
                        // Also just hide those pesky nils. (If you are vigilant enough you 
                        // see that the below compact map is intended to filter out one extra 
                        // nil emission from the .completed event.)
                        expect(observer.events.compactMap { $0.value.element }).to(equal(expected))
                    }
                }
            }
        }
	}
}

Instead if we write the proper matchers for Nimble we could just write:

// Same as above

                    it("emits sounds accordingly") {
                        let observer = testScheduler.createObserver(String.self)
                        
                        testScheduler
                            .createHotObservable(encounters)
                            .bind(to: dolphin.encounter)
                            .disposed(by: disposeBag)
                        
                        dolphin
                            .soundEmissions
                            .bind(to: observer)
                            .disposed(by: disposeBag)
                        
                        let expected: [Recorded<Event<String>>] = [
                            .next(200, "click"),
                            .next(300, "click"),
                            .next(400, "whistle"),
                            .completed(600)
                        ]
                        
                        testScheduler.start()
                        
                        expect(observer.events).to(equal(expected))
                    }
                    
// Same as above

Isn't it beautiful? It solves the problem of ignoring events and timing and spares us from the mapping misery, resulting in cleaner and more sophisticated tests.

Requirements

  • XCode 10.0
  • Swift 4.2

Installation

CocoaPods

Tested with pod --version: 1.6.1

# Podfile
use_frameworks!

target 'YOUR_TARGET_NAME' do
    pod 'NimbleRxTest',    '~> 1.0.0'
end

Replace YOUR_TARGET_NAME and then, in the Podfile directory, type:

$ pod install