Standing the Test of Time: The Date Provider Pattern

Post on 12-Apr-2017

27 views 0 download

Transcript of Standing the Test of Time: The Date Provider Pattern

STANDING THE TEST OF TIMETHE DATE PROVIDER PATTERN

DEREK LEE | TOKYO IOS MEETUP APRIL 2017

!

CALENDAR

TO DO

TRANSPORTATION

SHOPPING

DRUMMING

FLIGHT DEPARTUREHANEDA ✈ OKINAWAAPR-25-2017 7:12PM

HOW MUCH TIME DO I HAVEBEFORE MY DEPARTURE?

LETS START WITH A SIMPLE STRUCTstruct Flight { let flightNumber: String let departureCityCode: String let departureDateTime: Date let arrivalCityCode: String let arrivalDateTime: Date}

WE CAN EASILY INITIALIZE WITH SOME DATAlet tripHome = Flight( flightNumber: "JAL925", departureCityCode: "HND", departureDateTime: Date( timeIntervalSince1970: 1493115120 // 4-25-2017 19:12 ), arrivalCityCode: "OKA", arrivalDateTime: Date( timeIntervalSince1970: 1493125920 // 4-25-2017 22:12 ))

CALCULATING THE REMAINING TIME IS EASY...?func timeRemainingUntilDeparture() -> TimeInterval { let departureTimeInterval = departureDateTime.timeIntervalSinceNow

let currentTimeInterval = Date().timeIntervalSinceNow

return departureTimeInterval - currentTimeInterval}

AND WITH SOME QUICK FORMATTING...func formatTimeInterval(timeInterval: TimeInterval) -> String { let d = Int(timeInterval / 86400) let h = Int(timeInterval / 3600) % 24 let m = Int(timeInterval / 60) % 60 let s = Int(timeInterval) % 60

return "\(d) days, \(h) hours, \(m) minutes, and \(s) seconds"}

WE CAN EASILY OUTPUT THE REMAINING TIMElet timeIntervalUntilDeparture = tripHome.timeRemainingUntilDeparture()

let formattedRemainingTime = formatTimeInterval( timeInterval: timeIntervalUntilDeparture)

print("Time until departure: " + formattedRemainingTime)

TIME UNTIL DEPARTURE: 25 DAYS, 19 HOURS,45 MINUTES, AND 50 SECONDS

!

!HMMM....

!MAYBE WE SHOULD WRITE A TEST FOR THIS

func testTimeRemainingUntilDeparture_returnsCorrectTimeInterval() {

}

func testTimeRemainingUntilDeparture_returnsCorrectTimeInterval() { let tripHome = Flight( flightNumber: "JAL925", departureCityCode: "HND", departureDateTime: Date(timeIntervalSince1970: 1493115120), arrivalCityCode: "OKA", arrivalDateTime: Date(timeIntervalSince1970: 1493125920) )

}

func testTimeRemainingUntilDeparture_returnsCorrectTimeInterval() { let tripHome = Flight( flightNumber: "JAL925", departureCityCode: "HND", departureDateTime: Date(timeIntervalSince1970: 1493115120), arrivalCityCode: "OKA", arrivalDateTime: Date(timeIntervalSince1970: 1493125920) )

let remainingTime = tripHome.timeRemainingUntilDeparture()

}

func testTimeRemainingUntilDeparture_returnsCorrectTimeInterval() { let tripHome = Flight( flightNumber: "JAL925", departureCityCode: "HND", departureDateTime: Date(timeIntervalSince1970: 1493115120), arrivalCityCode: "OKA", arrivalDateTime: Date(timeIntervalSince1970: 1493125920) )

let remainingTime = tripHome.timeRemainingUntilDeparture()

XCTAssertEqual(Int(remainingTime), ???)}

func testTimeRemainingUntilDeparture_returnsCorrectTimeInterval() { let tripHome = Flight( flightNumber: "JAL925", departureCityCode: "HND", departureDateTime: Date(timeIntervalSince1970: 1493115120), arrivalCityCode: "OKA", arrivalDateTime: Date(timeIntervalSince1970: 1493125920) )

let remainingTime = tripHome.timeRemainingUntilDeparture()

XCTAssertEqual(Int(remainingTime), 2144133)}

WE RUN THE TEST AND...

HMM.

WELL... LETS UPDATE THE TEST THEN

func testTimeRemainingUntilDeparture_returnsCorrectTimeInterval() { let tripHome = Flight( flightNumber: "JAL925", departureCityCode: "HND", departureDateTime: Date(timeIntervalSince1970: 1493115120), arrivalCityCode: "OKA", arrivalDateTime: Date(timeIntervalSince1970: 1493125920) )

let remainingTime = tripHome.timeRemainingUntilDeparture()

XCTAssertEqual(Int(remainingTime), 2144113)}

!

WE NEED MORE CONTROL

HERE'S OUR CODE:func timeRemainingUntilDeparture() -> TimeInterval { let departureTimeInterval = departureDateTime.timeIntervalSinceNow

let currentTimeInterval = Date().timeIntervalSinceNow

return departureTimeInterval - currentTimeInterval}

WHAT IS IT THAT WE NEED MORE CONTROL OVER?func timeRemainingUntilDeparture() -> TimeInterval { let departureTimeInterval = departureDateTime.timeIntervalSinceNow

let currentTimeInterval = Date().timeIntervalSinceNow

return departureTimeInterval - currentTimeInterval}

THE CURRENT TIMElet currentTimeInterval = Date().timeIntervalSinceNow

THEREFORE, THE CURRENT TIME IS OUR DEPENDENCY

SO HOW CAN WE SOLVE THIS PROBLEM?

USING A DATE PROVIDER

DATE PROVIDER:AN OBJECT THAT WE CAN ASK TO GIVE US

THE CURRENT DATE & TIME

BACK TO OUR TEST THEN...

FIRST WE NEED TO PASS OUR DEPENDENCY INlet remainingTime = tripHome.timeRemainingUntilDeparture()

↓let remainingTime = tripHome.timeRemainingUntilDeparture( dateProvider: ???)

THIS CHANGES OUR METHOD'S DECLARATIONfunc timeRemainingUntilDeparture() -> TimeInterval

↓func timeRemainingUntilDeparture(dateProvider: DateProvider) -> TimeInterval

WHICH MEANS WE'LL NEED A PROTOCOLprotocol DateProvider {

}

... AND A WAY TO GET THE CURRENT DATE/TIMEprotocol DateProvider { func currentDateTime() -> Date}

WE'LL CREATE A FAKE OBJECT FOR TESTINGprotocol DateProvider { func currentDateTime() -> Date}

class FakeDateProvider: DateProvider {

func currentDateTime() -> Date {

}}

WHAT SHOULD THIS FUNCTION RETURN?protocol DateProvider { func currentDateTime() -> Date}

class FakeDateProvider: DateProvider {

func currentDateTime() -> Date { return ??? }}

METHOD NAME + "_RETURNVALUE"protocol DateProvider { func currentDateTime() -> Date}

class FakeDateProvider: DateProvider {

func currentDateTime() -> Date { return currentDateTime_returnValue }}

WHICH WE CAN DEFINE AND INITIALIZEprotocol DateProvider { func currentDateTime() -> Date}

class FakeDateProvider: DateProvider { var currentDateTime_returnValue = Date()

func currentDateTime() -> Date { return currentDateTime_returnValue }}

BACK TO OUR TEST... WE NEED TO PASS IN OUR FAKElet remainingTime = tripHome.timeRemainingUntilDeparture( dateProvider: ???)

BACK TO OUR TEST... WE NEED TO PASS IN OUR FAKElet remainingTime = tripHome.timeRemainingUntilDeparture( dateProvider: ???)

↓let fakeDateProvider = FakeDateProvider()let remainingTime = tripHome.timeRemainingUntilDeparture( dateProvider: fakeDateProvider)

NEXT IS THE MOST IMPORTANT STEP

SET THE VALUE YOU WANT THE FAKE TO RETURNfakeDateProvider.currentDateTime_returnValue = Date( timeIntervalSince1970: 1493028720 // 4-24-2017 19:12)

AND NOW WE CAN ADJUST OUR EXPECTATIONXCTAssertEqual(Int(remainingTime), 86400) // 24 Hours Prior

OUR UPDATED TEST:

func testTimeRemainingUntilDeparture_returnsCorrectTimeInterval() { let tripHome = Flight( flightNumber: "JAL925", departureCityCode: "HND", departureDateTime: Date(timeIntervalSince1970: 1493115120), arrivalCityCode: "OKA", arrivalDateTime: Date(timeIntervalSince1970: 1493125920) )

let fakeDateProvider = FakeDateProvider() fakeDateProvider.currentDateTime_returnValue = Date(timeIntervalSince1970: 1493028720)

let remainingTime = tripHome.timeRemainingUntilDeparture(dateProvider: fakeDateProvider)

XCTAssertEqual(Int(remainingTime), 86400)}

WHAT ABOUT THE ACTUAL IMPLEMENTATION?

THE DEFAULT IMPLEMENTATION IS EASYprotocol DateProvider { func currentDateTime() -> Date}

JUST RETURN THE CURRENT DATE & TIMEprotocol DateProvider { func currentDateTime() -> Date}

struct DefaultDateProvider: DateProvider { func currentDateTime() -> Date { return Date() }}

AND PASS IN THE DEFAULT TO YOUR ACTUAL CALL SITElet timeIntervalUntilDeparture = tripHome.timeRemainingUntilDeparture( dateProvider: DefaultDateProvider())

let formattedRemainingTime = formatTimeInterval( timeInterval: timeIntervalUntilDeparture)

print("Time until departure: " + formattedRemainingTime + " (" + String(timeIntervalUntilDeparture) + ")")

!

...

THIS IS ACTUALLY DEPENDENCY INJECTION

SUMMARY

▸ Identified our dependency (current date/time)▸ Created a protocol for it▸ Extracted it from our method

▸ Passed in the actual value at runtime▸ Created a fake we could use for testing

THANK YOU!@DEREKLEEROCK

GITHUB.COM/DEREKLEEROCK

Q&A: WHAT DO YOU USE FOR GETTING EPOCH DATES AND TIMES?

HTTPS://WWW.EPOCHCONVERTER.COM/MAC TERMINAL "DATE" COMMAND:

$ date -r 1 # Outputs Thu Jan 1 09:00:01 JST 1970

$ date -r 1493125920 # Outputs Tue Apr 25 22:12:00 JST 2017

$ date +%s # Outputs current date/time in # of seconds

SOURCE CODE IS AVAILABLE ON GITHUB:

https://github.com/derekleerock/StandingTheTestOfTime