How to write unit test for NSNotification How to write unit test for NSNotification swift swift

How to write unit test for NSNotification


XCTest has a class specifically for testing Notifications: XCTNSNotificationExpectation. You create one of these expectations, and it's fulfilled when a notification is received. You'd use it like:

// MyClass.swiftextension Notification.Name {    static var MyNotification = Notification.Name("com.MyCompany.MyApp.MyNotification")}class MyClass {    func sendNotification() {        NotificationCenter.default.post(name: .MyNotification,                                      object: self,                                    userInfo: nil)    }}// MyClassTests.swiftclass MyClassTests: XCTestCase {    let classUnderTest = MyClass()    func testNotification() {        let notificationExpectation = expectation(forNotification: .MyNotification,                                                            object: classUnderTest,                                                           handler: nil)        classUnderTest.sendNotification()        waitForExpectations(timeout: 5, handler: nil)    }}

XCTestCase's expectation(forNotification:object:handler:) is a convenience method to create an instance of XCTNSNotificationExpectation, but for more control, you could instantiate one and configure it yourself. See the docs.


The general solution is: Use dependency injection (DI) to make your components unit-testable. You can choose use a DI framework (I don't know if there is any good framework for Swift exists yet) or use native approach (i.e. pass object around)

One possible approach for your problem is to wrap NSNotificationCenter to make it mockable/injectable.

This is just a basic idea how you can decouple dependencies. Please don't just copy & paste the code below and expect it to work without understanding it.

import Foundationprotocol NotificationCenter {    func postNotificationName(name: String, object: AnyObject?)    // you can make it take the arguments as NSNotificationCenter.addObserver    func addObserver(callback: AnyObject? -> Void)}class MyNotificationCenter : NotificationCenter {    var _notificationCenter: NSNotificationCenter    init(_ center: NSNotificationCenter) {        _notificationCenter = center    }    func postNotificationName(name: String, object: AnyObject?) {        // call NSNotificationCenter.postNotificationName    }    func addObserver(callback: AnyObject? -> Void) {        // call NSNotificationCenter.addObserver    }}class MockNotificationCenter : NotificationCenter {    var postedNotifications: [(String, AnyObject?)] = []    var observers: [AnyObject? -> Void] = []    func postNotificationName(name: String, object: AnyObject?) {        postedNotifications.append((name, object))    }    func addObserver(callback: AnyObject? -> Void) {        observers.append(callback)    }}class MyView {    var notificationCenter: NotificationCenter    init(notificationCenter: NotificationCenter) {        self.notificationCenter = notificationCenter    }    func handleAction() {        self.notificationCenter.postNotificationName("name", object: nil)    }}class MyController {    var notificationCenter: NotificationCenter    init(notificationCenter: NotificationCenter) {        self.notificationCenter = notificationCenter    }    func viewDidLoad() {        self.notificationCenter.addObserver {            println($0)        }    }}

// production code// in AppDeletate.applicationDidFinishLaunchinglet notificationCenter = MyNotificationCenter(NSNotificationCenter.defaultCenter())// pass it to your root view controllerlet rootViewController = RootViewController(notificationCenter: notificationCenter)// orrootViewController.notificationCenter = notificationCenter// in controller viewDidLoadself.myView.notificationCenter = self.notificationCenter// when you need to create controller// pass notificationCenter to itlet controller = MyController(notificationCenter: notificationCenter)// in unit testfunc testMyView() {    let notificationCenter = MockNotificationCenter()    let myView = MyView(notificationCenter: notificationCenter)    // do something with myView, assert correct notification is posted    // by checking notificationCenter.postedNotifications}func testMyController() {    let notificationCenter = MockNotificationCenter()    let myController = MyController(notificationCenter: notificationCenter)    // assert notificationCenter.observers is not empty    // call it and assert correct action is performed}


Here is a simpler solution:

Step 1: Capture the notificationCenter object in an ambiant variable to be able to replace it with some spy class in your unit tests.

// In your production code:var notificationCenter = NSNotificationCenter.defaultCenter()// The code you are testing:notificationCenter.postNotificationName("notificationName", object: nil)

Step 2: Define your spy class using inheritance to be able to detect whether the notification was posted or not.

// In your test codeprivate class NotificationCenterSpy: NotificationCenter {    var notificationName: String?    override func post(_ notificationName: String, object anObject: Any?)     {        self.notificationName = aName    }}

Step 3: replace the ambiant variable in your unit test.

// In your test code:// Given// setup SUT as usual ...let notificationCenterSpy = NotificationCenterSpy()sut.notificationCenter = notificationCenterSpy// Whensut.loadView()// ThenXCTAssertEqual(notificationCenterSpy.notificationName, "notificationName")

Step 4: Testing the receiver View Controller

You should not test whether the receiver View Controller observes the change or not, you should test behaviour.

Something should be happening when the notification is received?That is what you should be testing, from your test code, post a notification and see if this behaviour happened (in your case if the page gets refreshed).