4

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 ?

Sam Spencer
  • 8,492
  • 12
  • 76
  • 133
Javier Cadiz
  • 12,326
  • 11
  • 55
  • 76

1 Answers1

3

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.

Gerd Castan
  • 6,275
  • 3
  • 44
  • 89
  • This approach looks very interesting but i have 2 main concerns: 1- How the testing target deals with healthkit autorizations ? . 2- What exactly we are testing since the stepsCount is something that is not tracked by the simulator or testing target ? Should i mock some data ? Any thoughts or suggestions here ? – Javier Cadiz Dec 01 '15 at 22:04
  • Part 1: I updated my official answer. Part 2: You have a health app in your simulator. Open this health app in your simulator and use it to add data points for steps: health app -> fitness -> steps -> add data points – Gerd Castan Dec 01 '15 at 22:28
  • Thanks, this is fantastic. – Javier Cadiz Dec 01 '15 at 22:40
  • 2
    Do you really want your unit tests to depend on manually inputting health kit data to the simulator? Sounds really flaky to me. All simulators, developers and CI systems would need to do this. Having that said, I just failed mocking HealthKit data. – Robert Gummesson Oct 23 '16 at 11:49
  • You have to manually authorize the first time. To my knowledge there is no way around that. After the first time it isn't necessary any more. – Gerd Castan Oct 23 '16 at 11:51