单元测试iOS 10通知

squ*_*rog 12 notifications unit-testing ios ios10

在我的应用程序中,我希望断言已以正确的格式添加通知.我通常会使用依赖注入来执行此操作,但我无法想到测试新UNUserNotificationCenterAPI的方法.

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

import Foundation
import UserNotifications

class NotificationCenterMock: UNUserNotificationCenter {
    var request: UNNotificationRequest? = nil
    override func add(_ request: UNNotificationRequest, withCompletionHandler completionHandler: ((Error?) -> Void)? = nil) {
        self.request = request
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,UNUserNotificationCenter没有可访问的初始化程序,我无法实例化模拟.

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

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

fra*_*a86 20

您可以为正在使用的方法创建协议,并在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
Run Code Online (Sandbox Code Playgroud)

请记住,每次使用新方法时,都必须将其添加到协议中,否则编译器会抱怨.

希望这可以帮助!