Search code examples
swiftunit-testingxctesthealthkithksamplequery

Unit Test HKSampleQuery in Swift


When I need to read data from HealthKit this is how my code looks like:

let stepsCount = HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount)

let stepsSampleQuery = HKSampleQuery(sampleType: stepsCount,
    predicate: nil,
    limit: 100,
    sortDescriptors: nil)
    { [unowned self] (query, results, error) in
        if let results = results as? [HKQuantitySample] {
            self.steps = results
            // Update some UI
        }
        self.activityIndicator.stopAnimating()
}

healthStore?.executeQuery(stepsSampleQuery)

This specific code was extracted from here for demo purpose.

So my question is:

How can I unit test this kind of code ?


Solution

  • I encapsulate this code in a function in a model class that knows nothing about the UI. It works like this:

    At the place the you have your

    // Update some UI
    

    call a completion closure, that was passed to the function using a parameter.

    You call this function from your controller class like this

    hkModel.selectSteps() {
        [unowned self] (query, results, error) in
        // update UI
    }
    

    This way you have a clean separation between your query logic in the model class and your UIController code.

    Now you can easily write a unit test calling the same method:

    func testSteps() {
        hkModel.selectSteps() {
            [unowned self] (query, results, error) in
            // XCTAssert(...)
        }
    }
    

    The last thing you need is to respect that your test code is called asynchronously:

    let stepExpectationEnd = expectationWithDescription("step Query")
    hkModel.selectSteps() {
        [unowned self] (query, results, error) in
        // XCTAssert(...)
        stepExpectationEnd.fulfill()
    }
    waitForExpectationsWithTimeout(10.0) {
        (error: NSError?) in
         if let error = error {
             XCTFail(error.localizedDescription)
         }
    }
    

    update

    Because you asked:

    I handle authorization at the test setup. looks like this:

    var healthData: HealthDataManager?
    override func setUp() {
        super.setUp()
        healthData = HealthDataManager()
        XCTAssert(healthData != nil, "healthDadta must be there")
    
        let authorizationAndAScheduleExpectation = expectationWithDescription("Wait for authorizatiion. Might be manual the first time")
        healthData?.authorizeHealthKit({ (success: Bool, error: NSError?) -> Void in
            print ("success: \(success) error \(error?.localizedDescription)")
            // fails on iPad
            XCTAssert(success, "authorization error \(error?.localizedDescription)")
    
            self.healthData?.scheduleAll() {
                (success:Bool, error:ErrorType?) -> Void in
                XCTAssert(success, "scheduleAll error \(error)")
    
                authorizationAndAScheduleExpectation.fulfill()
            }
        })
        waitForExpectationsWithTimeout(60.0) {
            error in
            if let error = error {
                XCTFail(error.localizedDescription)
            }
        }
    }
    

    The first time you run this code in a simulator, you have to approve authorization manually.

    After the first run the tests run without manual intervention.