How to Unit Test locale-specific local notifications
Asked Answered
A

4

8

I have just added local notifications to my app. These notifications are supposed to fire only if the app Locale's regionCode (i.e. Locale.current.regionCode) is "US" or "CA". I am not interested in the locale's language.

I will also want to write the complementary test case, but once I know how to write one test case, the other will follow naturally.

Therefore, my question is: How can a Locale be injected into the test (see testSuccessfulNotificationDelivery())?

LocalNotificationTests.swift:

class LocalNotificationTests: XCTestCase {

    let notification1 = LocalNotification(toTriggerInSeconds: 5)
    let notification2 = LocalNotification(toTriggerInSeconds: 6)

    // This object manages LocalNotifications 
    // by building them into UNUserNotifications
    // and then scheduling them using UNUserNotificationCenter.
    let notificationManager = NotificationManager()

    func testSuccessfulNotificationDelivery() {

        // setup locale and use it for testing, somehow
        let πŸ‡¨πŸ‡¦ = Locale(identifier: "en_CA")

        // The answer to my question would go here. 
        // (Inject Locale into the test, somehow?)

        notificationManager.schedule(notifications: [notification1, notification2], 
                                     withRegionCode: πŸ‡¨πŸ‡¦.regionCode)

        let expectation = self.expectation(description: "notification delivery")

        var deliveredNotifications: [UNNotification]?

        UNUserNotificationCenter.current().getDeliveredNotifications {
            deliveredNotifications = $0
            expectation.fulfill()
        }

        waitForExpectations(timeout: 10, handler: nil)

        XCTAssertEqual(deliveredNotifications?.count, 2) 
    }
}

Assume default setup() and tearDown().

Alita answered 23/5, 2019 at 12:36 Comment(5)
Does NotificationManager check the Locale and schedules the notifications only if it is en_US ? – Cw
NotificationManager has a method called schedule() that checks Locale.current.regionCode. It also has another version of schedule() where I can pass in the regionCode as a parameter and it will use that region to decide whether to schedule or not. I will edit my question to reflect this! – Alita
What would you like to unit test? If you call schedule() with the correct Locale and verify that the notification is sent, would that be satisfactory? I don't exactly understand the problem.. – Cw
@Cw the point of this unit test is to ensure that a user that is not in the US or Canada will not receive a notification. In this unit test, I want to inject a specific locale and confirm that the notification will/will not be scheduled based on this Locale. My notification manager correctly handles either case, I just have no idea how to programmatically simulate a locale that is different than what is already set up by XCode during testing. – Alita
Just assume that my notification manager works as intended. How would you write this unit test to ensure so that a user in ITALY does not get the notification? And to answer your question, it would be satisfactory to know that the notification has been sent if the locale is correct. However by default, the simulator uses the simulator's Locale.current.regionCode of US. What if I want to test that the manager works correctly with a regionCode set to CA, WITHOUT manually setting the region of the simulator to Canada every single time I want to test my code? – Alita
C
1
class LocalNotificationTests: XCTestCase {

let notification1 = LocalNotification(toTriggerInSeconds: 5)
let notification2 = LocalNotification(toTriggerInSeconds: 6)

// This object manages LocalNotifications 
// by building them into UNUserNotifications
// and then scheduling them using UNUserNotificationCenter.
let notificationManager = NotificationManager()

func testSuccessfulCanadaNotificationDelivery() {
    let canadaLocale = Locale("en_CA")
    XCTAssertTrue(notificationDelivered(with: canadaLocale))
}

func testNotificationDeliveryFailure() {
    let notCanadaOrUs = Locale("ru_RU")
    XCTAssertFalse(notificationDelivered(with: notCanadaOrUs))
}


private func notificationDelivered(with locale: Locale) -> Bool {

    // The answer to my question would go here. 
    // (Inject Locale into the test, somehow?)

    notificationManager.schedule(notifications: [notification1, notification2], 
                                 withRegionCode: locale.regionCode)

    let expectation = self.expectation(description: "notification delivery")

    var deliveredNotifications: [UNNotification]?

    UNUserNotificationCenter.current().getDeliveredNotifications {
        deliveredNotifications = $0
        expectation.fulfill()
    }

    waitForExpectations(timeout: 10, handler: nil)

    return (deliveredNotifications?.count ?? 0) == 2
}

Can you do something like this?

Caldera answered 2/6, 2019 at 14:24 Comment(2)
I just saw this. I don't know if it's going to work or not but with only 24 minutes left before the bounty expires, I'm going to award this bounty to you because the solution you provided seems clean and simple. I will mark it as the approved answer later on today or tomorrow if I was able to get it to work – Alita
Update for the curious: This alternative worked. Thank you @RinaLiu! – Alita
T
3

If you edit your scheme and select Test/Options, you can set the Application Region:
enter image description hereWould it be enough to have different schemes for manual testing?

Also, if you create a bot, you can set the build configuration so that it selects a certain Region: enter image description here If you define different bots, you could run the tests automatically.

Taciturn answered 31/5, 2019 at 7:7 Comment(0)
C
1
class LocalNotificationTests: XCTestCase {

let notification1 = LocalNotification(toTriggerInSeconds: 5)
let notification2 = LocalNotification(toTriggerInSeconds: 6)

// This object manages LocalNotifications 
// by building them into UNUserNotifications
// and then scheduling them using UNUserNotificationCenter.
let notificationManager = NotificationManager()

func testSuccessfulCanadaNotificationDelivery() {
    let canadaLocale = Locale("en_CA")
    XCTAssertTrue(notificationDelivered(with: canadaLocale))
}

func testNotificationDeliveryFailure() {
    let notCanadaOrUs = Locale("ru_RU")
    XCTAssertFalse(notificationDelivered(with: notCanadaOrUs))
}


private func notificationDelivered(with locale: Locale) -> Bool {

    // The answer to my question would go here. 
    // (Inject Locale into the test, somehow?)

    notificationManager.schedule(notifications: [notification1, notification2], 
                                 withRegionCode: locale.regionCode)

    let expectation = self.expectation(description: "notification delivery")

    var deliveredNotifications: [UNNotification]?

    UNUserNotificationCenter.current().getDeliveredNotifications {
        deliveredNotifications = $0
        expectation.fulfill()
    }

    waitForExpectations(timeout: 10, handler: nil)

    return (deliveredNotifications?.count ?? 0) == 2
}

Can you do something like this?

Caldera answered 2/6, 2019 at 14:24 Comment(2)
I just saw this. I don't know if it's going to work or not but with only 24 minutes left before the bounty expires, I'm going to award this bounty to you because the solution you provided seems clean and simple. I will mark it as the approved answer later on today or tomorrow if I was able to get it to work – Alita
Update for the curious: This alternative worked. Thank you @RinaLiu! – Alita
U
0

It could not exactly satisfy your needs but you can check this post.

It seems an important note about changing location style in your test cases.

Once we have executed test in simulator or device it can carry the same language and locale to the next XCUI test case. There is no way to clear the simulator content in between the test cases so it’s good idea to create new scheme for each country and locale

And also you can check Apple Documentation

Uncovenanted answered 29/5, 2019 at 14:6 Comment(0)
S
-1

I am not sure if you can do that with "XCTest", but I am sure you can do this with "XCUITest". You can probably do it something like this?

class CALocalNotificationTests: LocalNotificationTests {
    override func setUp() {
        super.setUp()

        let app = XCUIApplication()
        app.launchArguments = ["-AppleLocale", "en_CA"]
        app.launch()
    }

    func testSuccessfulNotificationDelivery() {
        testSuccessfulNotificationDelivery(with: Locale(identifier: "en_CA"))
    }
}

class LocalNotificationTests: XCTestCase {

    let notification1 = LocalNotification(toTriggerInSeconds: 5)
    let notification2 = LocalNotification(toTriggerInSeconds: 6)
//
//    // This object manages LocalNotifications
//    // by building them into UNUserNotifications
//    // and then scheduling them using UNUserNotificationCenter.
    let notificationManager = NotificationManager()

    func testSuccessfulNotificationDelivery(with locale: Locale) {

        notificationManager.schedule(notifications: [notification1, notification2],
                                     withRegionCode: locale.regionCode)

        let expectation = self.expectation(description: "notification delivery")

        var deliveredNotifications: [UNNotification]?

        UNUserNotificationCenter.current().getDeliveredNotifications {
            deliveredNotifications = $0
            expectation.fulfill()
        }

        waitForExpectations(timeout: 10, handler: nil)

        XCTAssertEqual(deliveredNotifications?.count, 2)
    }
}

I think it's quite trivial but app.launchArguments = ["-AppleLocale", "en_CA"] is the key.

You can create different test (sub)classes for different locales.

Swint answered 31/5, 2019 at 10:34 Comment(0)

© 2022 - 2024 β€” McMap. All rights reserved.