在什么情况下会在 swift 测试中使用 expectationForNotification

In what situation would one use expectationForNotification in swift testing

我有点困惑,因为 what/when 与 expectationForNotification as opposed toexpectationWithDescription` 有关。我一直无法在 swift 中找到任何明确的示例,说明您何时以及如何处理此调用。

我假设它可能是为了测试通知,但看起来它可能只是对整个 addObserver() 通知中心调用的更方便的包装。

有人可以简要解释一下它的作用、何时使用它,也许还有几行示例代码吗?

如您所想,expectationForNotification 是一种方便的期望,用于检查是否已发出通知。

本次测试:

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)
}

可以换成这个:

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

你可以在这个Objc.io number中找到很好的解释。

为了理解 expectation(forNotification:, object:, handler:) and expectation(description:) 之间的区别,我构建了一个简单的 XCTestCase subclass 和 Swift 3.

在这里,我们要测试发布 NotificationBlockOperation 是否使用请求的值更新我们 class 的指定 Int? 属性 50.


1。使用 expectation(description:)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。使用 expectation(description:)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。使用 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;博士

在我们的测试用例中使用 expectation(forNotification: String, object:, handler:) 而不是 expectation(description:) 提供了一些优势:

  • 我们的测试现在需要更少的代码行(31 行而不是 35 或 37 行),
  • 我们的测试不再需要将 addObserver(_:, selector:, name:, object:)#selectoraddObserver(forName:, object:, queue:, using:)
  • 一起使用
  • 我们的测试不再需要将 XCTestExpectation 实例声明为我们 class 的 属性 或我们测试方法的作用域变量并将其标记为已经在某个时候遇到了 fulfill().

你不应该依赖 UIKit 的 NotificationCenter。为您的类型划定界限,仅当您的类型将命令发送到正确的对象时才进行测试。这是一个示例,说明如何使 NotificationCenter 采用您的代码。 (我现在无法访问 Xcode,所以它可能有一些错字)

protocol NotificationCenterProtocol {
  func post(notification: Notification)
}

extension NotificationCenter: NotificationCenterProtocol {}

class SpyNotificationCenter: NotificationCenterProtocol {
  var didPostNotification = false

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

}