Search code examples
unit-testingswiftmockingnsdatemethod-swizzling

How to mock NSDate in Swift?


I have to test some date calculation but to do so I need to mock NSDate() in Swift. Whole app is written in Swift and I'd like to write test in it as well.

I've tried method swizzling but it doesn't work (or I'm doing something wrong which is more likely).

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

test:

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()
// ...
}

but date is always current date.


Solution

  • Disclaimer -- I'm new to Swift testing so this may be a horribly hacky solution, but I've been struggling with this, too, so hopefully this will help someone out.

    I found this explanation to be a huge help.

    I had to create a buffer class between NSDate and my code:

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

    then used the buffer class in any code that used NSDate(), providing the default DateHandler() as an optional argument.

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

    Then in the test create a mock that inherits from the original DateHandler(), and "inject" that into the code to be tested:

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