简体   繁体   English

如何为 NSNotification 编写单元测试

[英]How to write unit test for NSNotification

I am working in swift, I want to refresh a page so I am sending it using notification, I am posting a notification in one ViewController and adding observer in another and it is working perfectly.我正在快速工作,我想刷新一个页面,所以我使用通知发送它,我在一个 ViewController 中发布一个通知并在另一个 ViewController 中添加观察者,它运行良好。 What I want to do is add unit test to it in swift.我想要做的是快速添加单元测试。 I checked many sites but was not able to do it.我检查了许多网站,但无法做到。 I am new to swift and don't know where to start.我是 swift 的新手,不知道从哪里开始。

Basically the working is, when i click the button notification is posted and when the next view controller is loaded the notification observer is added.基本上工作是,当我点击按钮通知被发布,当下一个视图控制器被加载时,通知观察者被添加。

How can I do the unit testing我如何进行单元测试

Thanks in advance提前致谢

Edit: Code编辑:代码

NSNotificationCenter.defaultCenter().postNotificationName("notificationName", object: nil)

and adding observer as并将观察者添加为

NSNotificationCenter.defaultCenter().addObserver(self, selector: "vvv:",name:"notificationName", object: nil)

XCTest has a class specifically for testing Notifications: XCTNSNotificationExpectation . XCTest有一个专门用于测试通知的类: XCTNSNotificationExpectation You create one of these expectations, and it's fulfilled when a notification is received.您创建这些期望之一,并在收到通知时满足。 You'd use it like:你会像这样使用它:

// MyClass.swift
extension 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.swift
class 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. XCTestCaseexpectation(forNotification:object:handler:)是一种创建XCTNSNotificationExpectation实例的便捷方法,但为了更多控制,您可以实例化一个并自己配置它。 See the docs .请参阅文档

The general solution is: Use dependency injection (DI) to make your components unit-testable.一般的解决方案是:使用依赖注入 (DI) 使您的组件可进行单元测试。 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 (ie pass object around)您可以选择使用 DI 框架(我不知道是否有适合 Swift 的好的框架)或使用本机方法(即传递对象)

One possible approach for your problem is to wrap NSNotificationCenter to make it mockable/injectable.解决您的问题的一种可能方法是包装NSNotificationCenter以使其可模拟/可注入。

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 Foundation

protocol 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.applicationDidFinishLaunching
let notificationCenter = MyNotificationCenter(NSNotificationCenter.defaultCenter())

// pass it to your root view controller
let rootViewController = RootViewController(notificationCenter: notificationCenter)
// or
rootViewController.notificationCenter = notificationCenter

// in controller viewDidLoad
self.myView.notificationCenter = self.notificationCenter

// when you need to create controller
// pass notificationCenter to it
let controller = MyController(notificationCenter: notificationCenter)

// in unit test

func 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.第 1 步:在环境变量中捕获 notificationCenter 对象,以便能够在您的单元测试中用一些间谍类替换它。

// 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.第 2 步:使用继承定义您的间谍类,以便能够检测通知是否已发布。

// In your test code
private 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.第 3 步:替换单元测试中的环境变量。

// In your test code:

// Given
// setup SUT as usual ...
let notificationCenterSpy = NotificationCenterSpy()
sut.notificationCenter = notificationCenterSpy

// When
sut.loadView()

// Then
XCTAssertEqual(notificationCenterSpy.notificationName, "notificationName")

Step 4: Testing the receiver View Controller第 4 步:测试接收器视图控制器

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).这就是您应该测试的内容,从您的测试代码中发布通知并查看是否发生了这种行为(在您的情况下是否刷新了页面)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM