😬
[Swift] NotificationCenterのUnitTest
TL, DR
-
NotificationCenter.post
をXCTestでUnitTestする方法を記事にする。(以下のメソッド/クラスを使用)
NotificationCenterとは
- 情報をブロードキャストするときに使用できるクラス
- 他のオブジェクトに対して、そのオブジェクトへの参照を持ってなくても、通知を送れる。
UnitTestの方法
前提
以下のようなクラスを考える。
(Login/Logoutをしたときに、NotificationCenter.post
を呼ぶ想定)
let loginName = Notification.Name("login")
let logoutName = Notification.Name("logout")
class SenderClass {
func postLogin() {
NotificationCenter.default.post(name: loginName, object: self)
}
func postLogout() {
NotificationCenter.default.post(name: logoutName, object: self)
}
func postLoginWithUserInfo() {
NotificationCenter.default.post(name: loginName, object: self, userInfo: ["userId" : "000"])
}
expectation(forNotification:object:handler:)を使う
XCTestに用意されているメソッドで、特定のオブジェクトから特定のNotificationを受け取ったときに満たされるexpectationを作成する。
func expectation(
forNotification notificationName: NSNotification.Name,
object objectToObserve: Any?,
handler: XCTNSNotificationExpectation.Handler? = nil
) -> XCTestExpectation
-
forNotification
: テストしたいNotificationの名前 -
object
: テストしたいオブジェクト(このオブジェクトからのNotificationがテストの対象となる) -
handler
: Notificationを受け取ったときに実行する処理
例1: Notificationが送られたかをテストする
func testSenderClassPostLogin() {
// Setup
let sut = SenderClass()
let expectation = expectation(forNotification: loginName, object: nil)
// Act
sut.postLogin() // `loginName`のNotificationを送信
// Assert
wait(for: [expectation], timeout: 1.0)
}
-
Act
のところで、かわりにsut.postLogout()
を呼ぶとテストは失敗する。(logoutName
のNotificationを送っても、expectationは満たされないため)
例2: 特定のオブジェクトからNotificationが送られたかをテストする(失敗するテスト)
func testSenderClassPostLoginFromSpecificObject() {
// Setup
let sut1 = SenderClass()
let sut2 = SenderClass()
let expectation = expectation(forNotification: loginName, object: sut1)
// Act
sut2.postLogin()
// Assert
wait(for: [expectation], timeout: 1.0) //<-- `sut1`からのNotificationを待っているので、`sut2`からNotificationが送られてもexpectationは満たされない
}
-
Act
のところで、かわりにsut1.postLogin()
を呼ぶとテストは成功する。 -
expectation(forNotification: loginName, object: nil)
とすると、どのオブジェクトから送られたかはチェックされない(ref: 例1)
UserInfo
をテストする
例3: Notificationのfunc testSenderClassPostWithObject() {
// Setup
let sut = SenderClass()
let expectation = expectation(forNotification: loginName, object: nil) { notification in
XCTAssertEqual(notification.userInfo?["userId"] as? String, "000")
return true
}
// Act
sut.postLoginWithUserInfo() //<- `["userId":"000"]`を送信
// Assert
wait(for: [expectation], timeout: 1.0)
}
-
handler
の型は(Notification)->Bool
となっていて、handler
がfalse
を返すとテストは失敗する。
XCTNSNotificationExpectation
- expectation(forNotification:object:handler:)のdiscussionで、使いやすさの面で推奨されている方法。
-
expectation(forNotification:object:handler:)
と同じように使える。 - 違いは、
Excentation
に以下のプロパティが用意されている点-
var notificationName: NSNotification.Name
: チェックするNotificationの名前 -
var observedObject: Any?
: テストしたいオブジェクト(このオブジェクトからのNotificationがテストの対象となる) -
var notificationCenter: NotificationCenter
: どのNotificationCenterから送られたかをチェックする
-
UserInfo
をテストする(Handlerを使う例)
例4:Notificationのfunc testSenderClassPostLogin_XCTNSNotificationExpectation_handler() {
// Setup
let sut = SenderClass()
let expectation = XCTNSNotificationExpectation(name: loginName, object: sut)
expectation.handler = { notification in
XCTAssertEqual(notification.userInfo?["userId"] as? String, "000")
return true
}
// Act
sut.postLoginWithUserInfo()
// Assert
wait(for: [expectation], timeout: 1.0)
}
- 使い方は、例3と同じ
ObservedObject
にアクセスする
例5: func testSenderClassPostLogin_XCTNSNotificationExpectation() {
// Setup
let sut = SenderClass()
let expectation = XCTNSNotificationExpectation(name: loginName, object: sut)
// Act
sut.postLoginWithUserInfo()
// Assert
wait(for: [expectation], timeout: 1.0)
let object = expectation.observedObject as? SenderClass
}
- うまい使い方が思いつかなかったが、Notificationを送ったオブジェクトにアクセスできる。
Discussion