12

I need to create some unit test for delegate/protocol call backs. Here is an example of the implementation I'm trying to test:

protocol SomethingWithNumbersDelegate: class {

    func somethingWithDelegate(results:Int)
}

class SomethingWithNumbers {
    var delegate: SomethingWithNumbersDelegate? = nil
    func doAsyncStuffWithNumbers(number:Int)  {

        var numbers = Int()
        /*
         doing some with the input
         */
        self.delegate?.somethingWithDelegate(results: numbers)
    }
}

I haven't found a create the unit test (XCTest) to test the delegate response.

I'll really appreciate your help.

Paulo Mattos
  • 18,845
  • 10
  • 77
  • 85
user2924482
  • 8,380
  • 23
  • 89
  • 173

1 Answers1

31

You can use the XCTestExpectation facility for this. For instance:

class NumbersTest: XCTestCase, SomethingWithNumbersDelegate {

    func testAsynchronousNumbers() {
        numbersExpectation = expectation(description: "Numbers")

        let numbers = SomethingWithNumbers()
        numbers.delegate = self
        numbers.doAsyncStuffWithNumbers(number: 123)

        // Waits 100 seconds for results.
        // Timeout is always treated as a test failure.
        waitForExpectations(timeout: 100)
        XCTAssertEqual(self.results, 456)
    }

    private var numbersExpectation: XCTestExpectation!
    private var results: Int!

    func somethingWithDelegate(results: Int) {
        self.results = results
        numbersExpectation.fulfill()
    }
}

Asynchronous testing was made a lot easier with the introduction of expectations by Xcode 6. Expectations are created by helper methods on XCTestCase, such as:

func expectation(description: String) -> XCTestExpectation

Creates and returns an expectation associated with the test case.


Update. For those running Xcode 9, this is now a preferred idiom for waiting on a given XCTestExpectation instance (i.e., instead of the older waitForExpectations method):

wait(for: [numbersExpectation], timeout: 100)
Paulo Mattos
  • 18,845
  • 10
  • 77
  • 85
  • on this line " waitForExpectations(timeout: 10)"// I'm getting this error: nsinternalinconsistencyexception api violation call made to wait without any expectation having been set – user2924482 Apr 26 '17 at 23:46
  • @user2924482 Have you created the `numbersExpectation` object at start? – Paulo Mattos Apr 26 '17 at 23:49
  • I fix that error but now I'm getting this error: asynchronous wait failed exceeded timeout of 10 with unfulfilled expectations: "Numbers" – user2924482 Apr 27 '17 at 00:01
  • @user2924482 Increase the `timeout` arg from 10 seconds to 100 seconds, for instance. BTW, I updated the answer as well. – Paulo Mattos Apr 27 '17 at 00:03
  • @user2924482 Of course, your function `doAsyncStuffWithNumbers` must eventually *complete* in order for the test to succeed :) – Paulo Mattos Apr 27 '17 at 00:05
  • It's timing out because it never gets call back from the delegate. I add break points to the function in the protocol and seems like is never been call. – user2924482 Apr 27 '17 at 00:16
  • @user2924482 I recommend copying and pasting my `NumbersTest` class as is. You may have missed something... – Paulo Mattos Apr 27 '17 at 00:19
  • yes, I miss this class DelegateTest: XCTestCase,SomethingWithNumbersDelegate. Now is working. Thank your help – user2924482 Apr 27 '17 at 00:21
  • Wonderful Answer.! Can Anyone tell me why "private" is used for numbersExpectation and result.? – Yash Bedi Aug 11 '17 at 06:31
  • 1
    @YashBedi Since those properties are only needed inside of the `NumbersTests` class `private` is used to restrict the usage to that class only. If you create another subclass of `XCTestCase` it shouldn't need to access to those properties. No other type should have access to those properties, so it's best to use `private`. – Nick Kohrn Sep 20 '17 at 01:10