I'm a software developer based in the UK. I am blogging regularly about software development & Apple

Writing tests with Quick & Nimble

We all make mistakes and to make as little of them as possible we use testing frameworks. They enable us to deliver cleaner and better code. For us Swift programmers there aren’t really that many testing frameworks available. There is Kiwi, Specta / Expecta (with OCMock / Mockito) and there is Quick. The first two are really great if you just want to test Objective-C code, but when you need to test your Swift code there isn't really an alternative.

Since pure Swift (without @obc / dynamic) doesn't support the old mocking frameworks anymore, we need to take a different testing approach, a cleaner more generic approach by not relying on language features.

So what does that approach look like? It’s actually not one approach per se but a combination of different testing patterns which I'd like to talk about in the following sections.

  • Use Dependency Injection

Let’s say you have some component that handles all your network request. In the following example I use some code that gets a list of available cameras from the Transports for London API. It’s based on NSURLSession’s dataTaskWithURL and looks like this:

public class SBRequestManager {
    ...
    static let sharedManager = SBRequestManager()
    lazy private var session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
    
    public func tflDataWithURL(URL: NSURL ,
        completionBlock:((data : NSData?,error:NSError?) -> Void)) throws {
            let task = session.dataTaskWithURL(URL) { data,response , error in
                completionBlock(data: data,error: error)
            }
            task.resume()
    }
}

To test this code, I used a dedicated initializer which allows me to inject my session object.

convenience init(session : NSURLSession) {
    self.init()
    self.session = session
}

The mock object I inject using this initialiser sees all network requests and allows me to react to each of them individually. So I can test my code with

context("when calling tflDataWithURL") {
    var manager : SBRequestManager!
    var session: SBRequestManagerTestSession
    
    beforeEach() {
        session = SBRequestManagerTestSession()
        manager = SBRequestManager(session: session)
        ....
    };
    
    it("should use correct URL") {
        let expectation = "https://api.tfl.gov.uk/Place/Type/jamcam"
        let url = NSURL(string: expectation)
        try! manager.tflDataWithURL(url!) { _,_ in }
        let usedURLString = session.dataTaskURL!.absoluteString
        expect(usedURLString) == expectation
    }
}

You can find the implementation of the session object along with tests and the tfl application in my github repo.

  • Rely on public interfaces like protocols

Let’s say we want to test a viewcontroller that conforms to the UICollectionViewDataSource. So now there is no need to mock anything: the datasource methods are public and we have a clear understanding of what we expect:

context("when dealing with the collectionViewDataSource") {
    it("should return number of cameralistentries when calling collectionView:numberOfItemsInSection:") {
        let dataSource = controller as UICollectionViewDataSource
        let count = datasource.collectionView(controller.collectionView, numberOfItemsInSection section: 0)
        expect(count) == cameralistentries
    }
}

All it takes is a datasource object and to simply call its publicly available methods to verify our expectations.

  • Make properties publicly available

The easiest approach is sometimes the best. Often we just like to verify a certain state of an object or a variable without enabling code outside of the class to change it. It's a middle ground of making all class properties available with read/write access and just exposing the minimum public interface. In Swift we achieve this with the following statement

Public private(set) var variable : T

 

The patterns I just described here are of course not restricted in their use to Quick. They can be used with other frameworks as well.

What I really like about Quick and its pattern matcher Nimble is that they allow me to write and group my tests the way I used to. When I started with Quick, It was actually quite easy to convert my projects from Specta/Expecta to Quick & Nimble's Swift syntax. The added benefit was the clarity I gained by having my tests now written in a clear and concise manner e.g.

•    expect(list.count) == 3
•    expect(list).to(contain("JamCams_00001.01410 ")
•    expect(object).to(beNil()) or

What always bothered me with Kiwi and Specta was that I had to encapsulate my scalar variable in an object via @(scalar_variable).


Writing tests with Quick & Nimble in Swift however wasn’t always that straightforward. In general, if you want to test a class in Swift you have to make it available to the test target. Since Swift 2.0 you get away with a simple @testable import <target>. This is way better than what you needed to do before which was adding the class to test to the test target or declare the whole class as public.

The only minor issues I have with Quick & Nimble (although nothing new when you come from Kiwi et al) is that I am not able to single test my tests in XCode via the XCode UI in the form of the little play button. But here is a nice workaround: Just use the fit() statement instead of the it() statement e.g. fit(“should be this or that). fit() statements take priority over it() statements. If Quick finds fit() statements in your tests it only executes those tests and ignores everything else.

So what can I say? Writing tests with Quick & Nimble,  I am loving it.

 

 

 

 

 

 

 

 

Working with Instruments - Allocations

Objective-C protocols in Swift