单元测试iOS 10通知

单元测试iOS 10通知,ios,unit-testing,notifications,ios10,Ios,Unit Testing,Notifications,Ios10,在我的应用程序中,我希望声明已以正确的格式添加通知。我通常会使用依赖项注入来实现这一点,但我想不出一种方法来测试新的UNUserNotificationCenterAPI 我开始创建一个模拟对象,它将捕获通知请求: import Foundation import UserNotifications class NotificationCenterMock: UNUserNotificationCenter { var request: UNNotificationRequest? =

在我的应用程序中,我希望声明已以正确的格式添加通知。我通常会使用依赖项注入来实现这一点,但我想不出一种方法来测试新的
UNUserNotificationCenter
API

我开始创建一个模拟对象,它将捕获通知请求:

import Foundation
import UserNotifications

class NotificationCenterMock: UNUserNotificationCenter {
    var request: UNNotificationRequest? = nil
    override func add(_ request: UNNotificationRequest, withCompletionHandler completionHandler: ((Error?) -> Void)? = nil) {
        self.request = request
    }
}
但是,
UNUserNotificationCenter
没有可访问的初始值设定项,我无法实例化模拟

我甚至不确定是否可以通过添加通知请求和获取当前通知来进行测试,因为测试需要请求模拟器上的权限,这将导致测试暂停。目前,我已经将通知逻辑重构为一个包装器,所以我至少可以在整个应用程序中模拟它并手动测试


我有比手动测试更好的选择吗?

您可以为正在使用的方法创建一个协议,并在UNUserNotificationCenter上进行扩展以符合该协议。 此协议将充当原始UNUserNotificationCenter实现和模拟对象之间的“桥梁”,以替换其方法实现

下面是我在操场上编写的示例代码,效果很好:

/* UNUserNotificationCenterProtocol.swift */

// This protocol allows you to use UNUserNotificationCenter, and replace the implementation of its 
// methods in you test classes.
protocol UNUserNotificationCenterProtocol: class {
  // Declare only the methods that you'll be using.
  func add(_ request: UNNotificationRequest,
           withCompletionHandler completionHandler: ((Error?) -> Void)?)
}

// The mock class that you'll be using for your test classes. Replace the method contents with your mock
// objects.
class MockNotificationCenter: UNUserNotificationCenterProtocol {

  var addRequestExpectation: XCTestExpectation?

  func add(_ request: UNNotificationRequest,
           withCompletionHandler completionHandler: ((Error?) -> Void)?) {
    // Do anything you want here for your tests, fulfill the expectation to pass the test.
    addRequestExpectation?.fulfill()
    print("Mock center log")
    completionHandler?(nil)
  }
}

// Must extend UNUserNotificationCenter to conform to this protocol in order to use it in your class.
extension UNUserNotificationCenter: UNUserNotificationCenterProtocol {
// I'm only adding this implementation to show a log message in this example. In order to use the original implementation, don't add it here.
  func add(_ request: UNNotificationRequest, withCompletionHandler completionHandler: ((Error?) -> Void)?) {
    print("Notification center log")
    completionHandler?(nil)
  }
}

/* ExampleClass.swift */

class ExampleClass {

  // Even though the type is UNUserNotificationCenterProtocol, it will take UNUserNotificationCenter type
  // because of the extension above.
  var notificationCenter: UNUserNotificationCenterProtocol = UNUserNotificationCenter.current()

  func doSomething() {
    // Create a request.
    let content = UNNotificationContent()
    let request = UNNotificationRequest(identifier: "Request",
                                           content: content,
                                           trigger: nil)
    notificationCenter.add(request) { (error: Error?) in
      // completion handler code
    }
  }
}

let exampleClass = ExampleClass()
exampleClass.doSomething() // This should log "Notification center log"

EDITED:
/* TestClass.Swift (unit test class) */

class TestClass {
  // Class being tested 
  var exampleClass: ExampleClass!    
  // Create your mock class.
  var mockNotificationCenter = MockNotificationCenter()

  func setUp() {
     super.setUp()
     exampleClass = ExampleClass()
     exampleClass.notificationCenter = mockNotificationCenter 
  }

  func testDoSomething() {
    mockNotificationCenter.addRequestExpectation = expectation(description: "Add request should've been called")
    exampleClass.doSomething()
    waitForExpectations(timeout: 1)
  }
}
// Once you run the test, the expectation will be called and "Mock Center Log" will be printed
请记住,每次使用新方法时,都必须将其添加到协议中,否则编译器会抱怨


希望这有帮助

尽管测试调用了
UNUserNotificationCenter
而不是测试它是否实际工作(苹果应该测试它),这很可能是正确的,但是不需要任何权限来安排并检查计划通知。只有在实际显示通知时才需要权限(您肯定不会在单元测试中测试它)


在我的单元测试中,我调用real
UNUserNotificationCenter
实现,然后检查计划通知(
UNUserNotificationCenter.current().getPendingNotificationRequests
),所有这些都可以在没有任何权限的情况下工作,测试运行速度非常快。这种方法比已经提出的方法要快得多(从这个意义上说,您需要编写更少的代码才能进行测试)。

您可以利用
UNUserNotificationCenter
,然后在返回的
设置上
setValue

UNUserNotificationCenter.current().getNotificationSettings(completionHandler: { settings in
    let status: UNAuthorizationStatus = .authorized
    settings.setValue(status.rawValue, forKey: "authorizationStatus")
    completionHandler(settings)
})

这是一个很好的回应。您认为可以采用类似的方法来模拟
func getNotificationSettings(completionHandler:@escaping(UNNotificationSettings)->Swift.Void)
?我无法模拟返回的
UNNotificationSettings
对象,因为它无法实例化。这是一个好的测试吗?从上面的代码中,我可以看到TestClass有自己的doSomething(),这意味着它永远不会调用实际ExampleClass的doSomething()。这也意味着您可以在TestClass的doSomething()中编写任何内容并使其pass@JimmyB是的,你仍然可以这样做。您可以创建假UNNotificationSettings,但它还需要几个步骤。您不能实例化UNNotificationsSettings,因为它需要一个NSCoder,因此为此您还必须模拟NSCoder:p您可以通过子类化NSCoder并重写所需的方法来实现这一点:“allowsKeycoding”、“Decodein64”、“decodeObject”、“decodeBool”。一旦你有了它,把它传递给initonmockunnotification设置,你就完成了。您将遇到与UNNotification相同的问题,因此您可以通过相同的MockNSCoder。@kalehv这很好,因为我们没有测试UNNotificationCenter是否正常工作,我们只是想确保它正在被调用。因此,在本例中,如果创建一个XCTestException,可以在doSomething中调用.fulfill()。希望这是有意义的。@franciscojma86如果我错了,请纠正我,但我的印象是您的
TestClass
正在尝试测试
ExampleClass
是否确实调用了
add
。如果是这样的话,那么您的
TestClass
没有测试它。为什么要投反对票?我甚至没有提倡调用真实服务的方法,我只是对问题的作者所说的话做出了反应:“测试需要在模拟器上请求许可,这将导致测试暂停”。我澄清了事实并非如此,您不需要任何权限来检查计划通知。您实际上需要将请求附加到挂起通知队列的权限。再次检查不仅仅是我检查了一下,有一段时间我用这种方法在不同的设备上运行了自动测试。但这是3年前发布的,可能是较新的iOS版本改变了要求。现在我正在做一个完全不同的项目,而不是做任何事情。不过,如果我的话还不够,请检查:。权限(可能)仅用于提醒(显示/发出声音)通知。