Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In what situation would one use expectationForNotification in swift testing

I'm a little confused as what/when to do with expectationForNotification as opposed toexpectationWithDescription`. I've been unable to find any clear examples in swift for when and what you do with this call.

I'm assuming its perhaps to test notifications but it looks like it might just be a more convenient wrapper around the whole addObserver() call of notification center.

Could somebody give a brief explanation of what it does, when to use it, and perhaps a few lines of sample code?

like image 782
Jeef Avatar asked Apr 22 '15 15:04

Jeef


2 Answers

As you have already imagined expectationForNotification is a convenience expectation for checking if a notification was raised.

This test:

func testItShouldRaiseAPassNotificationV1() {
    let expectation = expectationWithDescription("Notification Raised")
    let sub = NSNotificationCenter.defaultCenter().addObserverForName("evPassed", object: nil, queue: nil) { (not) -> Void in
        expectation.fulfill()
    }
    NSNotificationCenter.defaultCenter().postNotificationName("evPassed", object: nil)
    waitForExpectationsWithTimeout(0.1, handler: nil)
    NSNotificationCenter.defaultCenter().removeObserver(sub)
}

can be replaced by this one:

func testItShouldRaiseAPassNotificationV2() {
    expectationForNotification("evPassed", object: nil, handler: nil)
    NSNotificationCenter.defaultCenter().postNotificationName("evPassed", object: nil)
    waitForExpectationsWithTimeout(0.1, handler: nil)
}

You can find a good explanation in this Objc.io number.

like image 85
Giordano Scalzo Avatar answered Oct 02 '22 13:10

Giordano Scalzo


In order to understand the difference between expectation(forNotification:, object:, handler:) and expectation(description:), I have build a simple XCTestCase subclass with Swift 3.

Here, we want to test that a BlockOperation that posts a Notification updates a specified Int? property of our class with the requested value of 50.


1. Using expectation(description:) with addObserver(_:, selector:, name:, object:)

import XCTest

class AppTests: XCTestCase {

    var testExpectation: XCTestExpectation?
    var finalAmount: Int?

    func testFinalAmount() {
        let notificationName = Notification.Name(rawValue: "BlockNotification")

        // Set self as an observer
        let selector = #selector(updateFrom(notification:))
        NotificationCenter.default.addObserver(self, selector: selector, name: notificationName, object: nil)

        // Set expectation
        testExpectation = expectation(description: "Did finish operation expectation")

        // Set and launch operation block and wait for expectations
        let operation = BlockOperation(block: {
            NotificationCenter.default.post(name: notificationName, object: nil, userInfo: ["amount": 50])
        })
        operation.start()
        waitForExpectations(timeout: 3, handler: nil)

        // Asserts
        XCTAssertNotNil(finalAmount)
        XCTAssertEqual(finalAmount, 50)
    }

    func updateFrom(notification: Notification) {
        if let amount = notification.userInfo?["amount"] as? Int {
            self.finalAmount = amount
        }
        self.testExpectation?.fulfill()
    }

}

2. Using expectation(description:) with addObserver(forName:, object:, queue:, using:)

import XCTest

class AppTests: XCTestCase {

    var finalAmount: Int?

    func testFinalAmount() {
        let notificationName = Notification.Name(rawValue: "BlockNotification")

        // Set expectation
        let testExpectation = expectation(description: "Did finish operation expectation")

        // Set self as an observer
        let handler = { (notification: Notification) -> Void in
            if let amount = notification.userInfo?["amount"] as? Int {
                self.finalAmount = amount
            }
            testExpectation.fulfill()
        }
        NotificationCenter.default.addObserver(forName: notificationName, object: nil, queue: nil, using: handler)

        // Set and launch operation block and wait for expectations
        let operation = BlockOperation(block: {
            NotificationCenter.default.post(name: notificationName, object: nil, userInfo: ["amount": 50])
        })
        operation.start()
        waitForExpectations(timeout: 3, handler: nil)

        // Asserts
        XCTAssertNotNil(finalAmount)
        XCTAssertEqual(finalAmount, 50)
    }

}

3. Using expectation(forNotification:, object:, handler:)

import XCTest

class AppTests: XCTestCase {

    var finalAmount: Int?

    func testFinalAmount() {
        let notificationName = Notification.Name(rawValue: "BlockNotification")

        // Set expectation
        let handler = { (notification: Notification) -> Bool in
            if let amount = notification.userInfo?["amount"] as? Int {
                self.finalAmount = amount
            }
            return true
        }
        expectation(forNotification: notificationName.rawValue, object: nil, handler: handler)

        // Set and launch operation block and wait for expectations
        let operation = BlockOperation(block: {
            NotificationCenter.default.post(name: notificationName, object: nil, userInfo: ["amount": 50])
        })
        operation.start()
        waitForExpectations(timeout: 3, handler: nil)

        // Asserts
        XCTAssertNotNil(finalAmount)
        XCTAssertEqual(finalAmount, 50)
    }

}

tl;dr

Using expectation(forNotification: String, object:, handler:) instead of expectation(description:) in our test case provides some advantages:

  • our test now requires less lines of code (31 instead of 35 or 37 lines),
  • our test does not require anymore to use addObserver(_:, selector:, name:, object:) with a #selector or addObserver(forName:, object:, queue:, using:),
  • our test does not require anymore to declare an XCTestExpectation instance as a property of our class or as a scoped variable of our test method and to mark it as having been met at some point with fulfill().
like image 27
Imanou Petit Avatar answered Oct 02 '22 12:10

Imanou Petit