【问题标题】:How do you mock default timezone during unit test?在单元测试期间如何模拟默认时区?
【发布时间】:2021-10-28 02:01:04
【问题描述】:

在单元测试期间,我想临时模拟特定国家/地区的默认时区。这就是我目前所做的,通过使用全局变量。

应用代码

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
        }
    }
}

单元测试

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)

虽然可行,但我不喜欢这样的方式

  1. 使用了全局变量
  2. if 检查的额外运行时成本

我是否有更好的方法对特定时区执行单元测试?

【问题讨论】:

  • 如果应用通过TimeZone.current获取时区,那么测试的方法就是模拟TimeZone。
  • 该应用使用自定义TimeZone.current()(不是TimeZone.current),以便我们可以完全控制返回的内容。您能否详细说明我们如何“模拟”TimeZone?
  • 我是说使用current。 ——你明白什么是嘲讽吗? “如果”只在应用程序启动时发生一次。如果这是我们只使用真实时区的应用程序。如果这是一个测试,我们使用具有相同接口的模拟对象,以便您可以控制从 current 返回的内容。
  • 更改方法签名,以便您可以注入时区,并将.current 作为默认值。然后在单元测试中你可以注入你想要测试的任何区域,这也将使你的测试更加灵活。
  • 或者更改方法签名,以便您可以注入类似 TimeZone 的对象,.current 消息可以发送到该对象;在真正的应用程序中是 TimeZone,但在测试中它将是你的模拟。 — 但是,如果您需要详细的帮助,您需要从您的应用程序代码中提供实际的详细信息。例如,如果目标是测试 ReminderUtils.toDayResolution(timeMillis),您需要向我们展示该方法。

标签: ios swift unit-testing


【解决方案1】:

TimeZone 是 Objective-C 的 NSTimeZone 的直接包装器,TimeZone.current 对应于 NSTimeZone.systemTimeZone

因此,您可以使用任何模拟框架模拟/存根NSTimeZone.systemTimeZone。这将帮助您摆脱代码库中讨厌的if

【讨论】:

  • 感谢您的提示!我发布了代码 sn-p 以显示它是如何完成的。
【解决方案2】:

可以通过修改NSTimeZone.default来完成模拟

// Cuba will advance its clock by 1 hour, during 2021-03-14 00:00 am. This means during 2021-03-13 23:59 pm, after
// next 1 second, the time will be 2021-03-14 01:00 am.
func testToDayResolutionInCuba() throws {
    let cubaTimeZone = TimeZone(identifier: "America/Havana")!
    let oldDefault = NSTimeZone.default
    NSTimeZone.default = cubaTimeZone
    defer {
        NSTimeZone.default = oldDefault
    }
    
    var dateComponents = DateComponents()
    dateComponents.year = 2021
    dateComponents.month = 3
    dateComponents.day = 14
    dateComponents.hour = 0
    dateComponents.minute = 0
    dateComponents.second = 0
    
    let calendar = Calendar.current
    let date = calendar.date(from: dateComponents)!
    
    let timeMillis = date.timeMillis
    
    let timestampWithoutTime = ReminderUtils.toDayResolution(timeMillis)
    
    let dateWithoutTime = Date(timeIntervalSince1970: Double(timestampWithoutTime/1000))
    let result = calendar.dateComponents(in: cubaTimeZone, from: dateWithoutTime)
    
    XCTAssertEqual(2021, result.year)
    XCTAssertEqual(3, result.month)
    XCTAssertEqual(14, result.day)
    XCTAssertEqual(1, result.hour)
    XCTAssertEqual(0, result.minute)
    XCTAssertEqual(0, result.second)
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-16
    • 1970-01-01
    • 1970-01-01
    • 2022-12-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多