In what situation would one use expectationForNotification in swift testing
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.
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
oraddObserver(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 withfulfill()
.
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
}
}