Search code examples
iosswiftunit-testing

How do you mock default timezone during unit test?


During unit test, I would like to temporary mock the default timezone to specific country. This is what I do currently, by using global variable.

Application code

import Foundation

var _TIME_ZONE_FOR_UNIT_TEST: TimeZone? = nil

extension TimeZone {
    static func current() -> TimeZone {
        if let _TIME_ZONE_FOR_UNIT_TEST = _TIME_ZONE_FOR_UNIT_TEST {
            return _TIME_ZONE_FOR_UNIT_TEST
        } else {
            return TimeZone.current
        }
    }
}

Unit test

func testToDayResolutionInCuba() throws {
    let cubaTimeZone = TimeZone(identifier: "America/Havana")!
    
    _TIME_ZONE_FOR_UNIT_TEST = cubaTimeZone
    defer {
        _TIME_ZONE_FOR_UNIT_TEST = nil
    }
    
    ...
    
    // ReminderUtils.toDayResolution is depending on TimeZone.current()
    let timestampWithoutTime = ReminderUtils.toDayResolution(timeMillis)

Although it works, I do not like such a way as

  1. Global variable is used
  2. Extra runtime cost for the if check

Is there a better way for me to perform unit test for a specific timezone?


Solution

  • This solution doesn't work on iOS 17, as Timezone.current no longer reaches to NSTimeZone.systemTimeZone in order to swizzle it.


    TimeZone is a direct wrapper over Objective-C's NSTimeZone, and TimeZone.current corresponds to NSTimeZone.systemTimeZone.

    Thus, you can mock/stub NSTimeZone.systemTimeZone with any mocking framework. This will help you get rid of the nasty if from your codebase.