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()
.