繁体   English   中英

如何在Swift中模拟NSDate?

[英]How to mock NSDate in Swift?

我必须测试一些日期计算,但为此我需要在Swift中模拟NSDate() 整个应用程序是用Swift编写的,我也想在其中编写测试。

我试过方法调整但它不起作用(或者我做错了什么更有可能)。

extension NSDate {
    func dateStub() -> NSDate {
        println("swizzzzzle")
        return NSDate(timeIntervalSince1970: 1429886412) // 24/04/2015 14:40:12
    }
}

测试:

func testCase() {
    let original = class_getInstanceMethod(NSDate.self.dynamicType, "init")
    let swizzled = class_getInstanceMethod(NSDate.self.dynamicType, "dateStub")
    method_exchangeImplementations(original, swizzled)
    let date = NSDate()
// ...
}

但是date总是当前日期。

免责声明 - 我是Swift测试的新手,所以这可能是一个可怕的hacky解决方案,但我也一直在努力解决这个问题,所以希望这会帮助别人。

我发现这个解释是一个巨大的帮助。

我不得不在NSDate和我的代码之间创建一个缓冲类:

class DateHandler {
   func currentDate() -> NSDate! {
      return NSDate()
   }
}

然后在任何使用NSDate()的代码中使用缓冲类,提供默认的DateHandler()作为可选参数。

class UsesADate {
   func fiveSecsFromNow(dateHandler: DateHandler = DateHandler()) -> NSDate! {
      return dateHandler.currentDate().dateByAddingTimeInterval(5)
   }
}

然后在测试中创建一个继承自原始DateHandler()的模拟器,并将其“注入”到要测试的代码中:

class programModelTests: XCTestCase {

    override func setUp() {
        super.setUp()

        class MockDateHandler:DateHandler {
            var mockedDate:NSDate! =    // whatever date you want to mock

            override func currentDate() -> NSDate! {
                return mockedDate
            }
        }
    }

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

    func testAddFiveSeconds() {
        let mockDateHandler = MockDateHandler()
        let newUsesADate = UsesADate()
        let resultToTest = usesADate.fiveSecondsFromNow(dateHandler: mockDateHandler)
        XCTAssertEqual(resultToTest, etc...)
    }
}

你应该真正设计你的系统以支持测试,而不是使用调配。 如果您进行了大量的数据处理,那么您应该将适当的日期注入使用它的函数中。 通过这种方式,您的测试将日期注入到这些函数中以测试它们,并且您还有其他测试可以验证是否会为各种其他情况注入正确的日期(当您使用日期的方法时)。

特别是对于你的混乱问题,IIRC NSDate是一个类集群,所以你要替换的方法不太可能被调用,因为另一个类将被“默默地”创建并返回。

如果你想调动它,你需要调动一个由NSDate内部使用的类,它是__NSPlaceholderDate 仅用于测试,因为它是私有API。

func timeTravel(to date: NSDate, block: () -> Void) {
    let customDateBlock: @convention(block) (AnyObject) -> NSDate = { _ in date }
    let implementation = imp_implementationWithBlock(unsafeBitCast(customDateBlock, AnyObject.self))
    let method = class_getInstanceMethod(NSClassFromString("__NSPlaceholderDate"), #selector(NSObject.init))
    let oldImplementation = method_getImplementation(method)
    method_setImplementation(method, implementation)
    block()
    method_setImplementation(method, oldImplementation)
}

以后你可以像这样使用:

let date = NSDate(timeIntervalSince1970: 946684800) // 2000-01-01
timeTravel(to: date) { 
    print(NSDate()) // 2000-01-01
}

正如其他人所建议的,我宁愿推荐一个类Clock或类似的,你可以传递并从中获取日期,你可以轻松地用你的测试中的替代实现替换它。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM