5

I am using test-driven development on Xcode and in Swift to develop an app.

I want to add timed notification alerts to remind people to come back to the app to perform an action. To do that I need to request authorization from my user using the notification center.

To do this, I want to write a test in my unit tests that only passes when the shared UNUserNotificationCenter instance calls its requestAuthorization(options:completionHandler:) method.

I have tried mocking UNUserNotificationCenter:

extension NotificationsExperimentsTests {

    class MockNotificationCentre: UNUserNotificationCenter {

        var didRequestAuthorization = false

        override func requestAuthorization(options: UNAuthorizationOptions = [], completionHandler: @escaping (Bool, Error?) -> Void) {
            didRequestAuthorization = true
        }
    }
}

But then when I try and initialise it in a test,

func test_requestAuthorization_IsCalled() {

    var mockNotificationCenter = MockNotificationCentre()
}

the compiler tells me that:

'NotificationsExperimentsTests.MockNotificationCentre' cannot be constructed because it has no accessible initializers

I'm not sure what to try next, or even whether what I'm trying to do is possible?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Kramer
  • 338
  • 2
  • 15
  • Does this help you? https://stackoverflow.com/questions/40862551/unit-testing-ios-10-notifications – Ramy Al Zuhouri Nov 20 '19 at 18:00
  • Thank you, unfortunately not, I've been looking at it. With some syntax revisions it works fine in a Playground, but when I transfer it to separate test and build targets it won't compile. – Kramer Nov 20 '19 at 18:09
  • Can you copy the compiler errors? – Ramy Al Zuhouri Nov 20 '19 at 18:10
  • If I define the MockNotificationCenter within an extension to my testClass then I get: "Cannot assign value of type 'testClass.MockNotificationCenter' to type 'UNUserNotificationCenter'" If I move the definition outside of the test class, I get essentially the same message: "Cannot assign value of type 'MockNotificationCenter' to type 'UNUserNotificationCenter'" – Kramer Nov 20 '19 at 18:30

1 Answers1

0

So here's where I'm at so far. With thanks to this previous answer:

Unit testing iOS 10 notifications

My testclass:

import XCTest
import UserNotifications

@testable import NotificationsExperiments

class TestClass: XCTestCase {

    override func setUp() {
     super.setUp()
  }

  func testDoSomething() {
        //Given
        // Class being tested
        let exampleClass = ExampleClass()
        // Create your mock class.
        let mockNotificationCenter = MockNotificationCenter()
        exampleClass.notificationCenter = mockNotificationCenter
    //When
        exampleClass.doSomething()
        //Then
        XCTAssertTrue(mockNotificationCenter.didRequestAuthorization)
  }
}


extension TestClass {

    class MockNotificationCenter: MockUserNotificationCenterProtocol {

        var didRequestAuthorization = false

        func requestAuthorization(options: UNAuthorizationOptions, completionHandler: ((Bool, Error?) -> Void)) {
            didRequestAuthorization = true
        }
    }
}

My Example class:

import Foundation
import UserNotifications

class ExampleClass {

    #if DEBUG
    var notificationCenter: MockUserNotificationCenterProtocol = UNUserNotificationCenter.current()
    #else
    var notificationCenter = UNUserNotificationCenter.current()
    #endif

    func doSomething() {
        let options: UNAuthorizationOptions = [.alert, .sound, .badge]
        notificationCenter.requestAuthorization(options) {
            (didAllow, error) in
            if !didAllow {
                print("User has declined notifications")
            }
        }
  }
}

#if DEBUG
protocol MockUserNotificationCenterProtocol: class {
    func requestAuthorization(options: UNAuthorizationOptions, completionHandler: ((Bool, Error?) -> Void))

}

extension UNUserNotificationCenter: MockUserNotificationCenterProtocol {
    func requestAuthorization(options: UNAuthorizationOptions, completionHandler: ((Bool, Error?) -> Void)) {
        print("Requested Authorization")
    }
}
#endif

This does work, but it's more than a bit hacky. In DEBUG mode it doesn't actually send a request for authorization, but it does when it's released.

Any further contributions would be gladly accepted.

Kramer
  • 338
  • 2
  • 15