2015-04-27 19 views
9

Devo testare qualche calcolo delle date ma per farlo devo prendere in giro NSDate() in Swift. L'intera app è scritta in Swift e mi piacerebbe anche scrivere un test.Come simulare NSDate in Swift?

Ho provato il metodo swizzling ma non funziona (o sto facendo qualcosa di sbagliato che è più probabile).

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

prova:

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

ma date è sempre data corrente.

+0

Perché non si spegne semplicemente l'aggiornamento automatico della data e dell'ora del sistema, si modifica la data del computer e si eseguono i test –

+2

Eseguiamo test sul mac remoto collegato alla soluzione CI. Non voglio fare troppo casino con quel computer. –

risposta

7

Disclaimer - Sono nuovo di Swift testing, quindi questa potrebbe essere una soluzione orribilmente hacky, ma ho anche dovuto lottare con questo, quindi spero che questo possa aiutare qualcuno.

Ho trovato this explanation come un aiuto enorme.

ho dovuto creare una classe cuscinetto tra NSDate e il mio codice:

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

poi utilizzato la classe di buffer in qualsiasi codice che ha utilizzato NSDate(), che fornisce il default DateHandler() come un argomento opzionale.

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

Poi nel test di creare un modello che eredita dalla DateHandler originale(), e "iniettare" che nel codice da testare:

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

Ho finito con soluzione simile quindi mi accetto questa risposta :) –

3

Anziché utilizzare la funzione di swizzling, è necessario progettare il sistema in modo che supporti il ​​test. Se si esegue un sacco di elaborazione dei dati, è necessario inserire la data appropriata nelle funzioni che la utilizzano. In questo modo il test inietta le date in queste funzioni per testarle e si hanno altri test che verificano l'iniezione delle date corrette (quando si stubano i metodi che usano le date) per varie altre situazioni.

In particolare per il tuo problema di swizzling, IIRC NSDate è un cluster di classe, quindi è improbabile che il metodo che stai sostituendo venga chiamato in quanto una classe diversa verrà "silenziata" creata e restituita.

3

Se si vuole Swizzle esso è necessario swizzle una classe che è internamente usata da NSDate ed è __NSPlaceholderDate. Utilizza questo solo per i test poiché si tratta di un'API privata.

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

E poi si può usare in questo modo:

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

Come altri hanno suggerito avrei preferito raccomandare l'introduzione di una classe Clock o simile che è possibile passare in giro e ottenere una data da esso e si può facilmente sostituirlo con un'implementazione alternativa nei test.

+0

si blocca su 32 simulatori di bit '# 1 \t 0x006ded32 a (namespace anonimo) :: :: AutoreleasePoolPage pop (void *)() ' –

+0

E don' funziona con Swift 3 e Date(), non riesco a capire come risolverlo –

+1

@ArkadiuszMatecki Non c'è modo di farlo funzionare con 'Date' introdotto in Swift perché è nativo di Swift e il swizzling non funziona con esso. È necessario utilizzare l'approccio con una classe di clock. –

Problemi correlati