😬

[Swift] NotificationCenterのUnitTest

2023/12/02に公開

TL, DR

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)

例3: NotificationのUserInfoをテストする

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となっていて、handlerfalseを返すとテストは失敗する。

XCTNSNotificationExpectation

  • expectation(forNotification:object:handler:)のdiscussionで、使いやすさの面で推奨されている方法。
  • expectation(forNotification:object:handler:)と同じように使える。
  • 違いは、Excentationに以下のプロパティが用意されている点
    • var notificationName: NSNotification.Name: チェックするNotificationの名前
    • var observedObject: Any?: テストしたいオブジェクト(このオブジェクトからのNotificationがテストの対象となる)
    • var notificationCenter: NotificationCenter: どのNotificationCenterから送られたかをチェックする

例4:NotificationのUserInfoをテストする(Handlerを使う例)

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と同じ

例5: ObservedObjectにアクセスする

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を送ったオブジェクトにアクセスできる。

References

Discussion