In what situation would one use expectationForNotification in swift testing
Asked Answered
L

3

13

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?

Lagrange answered 22/4, 2015 at 15:28 Comment(0)
N
30

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.

Norfolk answered 22/4, 2015 at 16:9 Comment(1)
Hi there, if i'm posting notification with userInfo, how should i test?Clubby
O
4

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().
Overstock answered 24/8, 2016 at 16:14 Comment(1)
Incredible answerJuvenescent
O
2

You should not depend on UIKit's NotificationCenter. Make a boundary of your type and test only if your type is sending the command to the right object. Here is an example of how you can make NotificationCenter adopts your code. (I can't access Xcode right now, so it may have some typo)

protocol NotificationCenterProtocol {
  func post(notification: Notification)
}

extension NotificationCenter: NotificationCenterProtocol {}

class SpyNotificationCenter: NotificationCenterProtocol {
  var didPostNotification = false

  func post(notification: Notification) {
    didPostNotification = true
  }

}
Oeildeboeuf answered 25/5, 2017 at 20:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.