1

I have an array of dates that I would like to get an NSDate object from, using an NSDateFormatter.

let dates = [
    "Tue, 04 Feb 2014 22:03:45 Z",
    "Sun, 05 Jun 2016 08:35:14 Z",
    "Sun, 05 Jun 2016 08:54 +0000",
    "Mon, 21 Mar 2016 13:31:23 GMT",
    "Sat, 04 Jun 2016 16:26:37 EDT",
    "Sat, 04 Jun 2016 11:55:28 PDT",
    "Sun, 5 Jun 2016 01:51:07 -0700",
    "Sun, 5 Jun 2016 01:30:30 -0700",
    "Thu, 02 June 2016 14:43:37 GMT",
    "Sun, 5 Jun 2016 01:49:56 -0700",
    "Fri, 27 May 2016 14:32:19 -0400",
    "Sun, 05 Jun 2016 01:45:00 -0700",
    "Sun, 05 Jun 2016 08:32:03 +0000",
    "Sat, 04 Jun 2016 22:33:02 +0000",
    "Sun, 05 Jun 2016 01:52:30 -0700",
    "Thu, 02 Jun 2016 15:24:37 +0000"
]

I was going with the GWT pattern, but felt strange structuring my unit test like that, because I need to write a dozen or more distinct test funtions for each case.

Can anyone suggest a better approach to this? Or is this an acceptable usage of the "Given, When, Then" pattern?

..I really don't want to have a testRFC822DateFormatter1(), testRFC822DateFormatter2(), testRFC822DateFormatter3(),...

func testRFC822DateFormatter() {

    // Given
    let rfc822DateFormatter = RFC822DateFormatter()
    let dateString = "Tue, 04 Feb 2014 22:03:45 Z"

    // When
    let date = rfc822DateFormatter.dateFromString(dateString)

    // Then
    XCTAssertNotNil(date)

    let components = NSCalendar.currentCalendar().components([.Year, .Month, .Day, .Hour, .Minute, .Second, .TimeZone, .Calendar], fromDate: date!)

    XCTAssertEqual(components.day, 4)
    XCTAssertEqual(components.month, 2)
    XCTAssertEqual(components.year, 2014)
    XCTAssertEqual(components.hour, 22)
    XCTAssertEqual(components.minute, 3)
    XCTAssertEqual(components.second, 45)
    XCTAssertEqual(components.timeZone?.daylightSavingTimeOffset, 3600)
    XCTAssertEqual(components.timeZone?.secondsFromGMT, 3600)
    XCTAssertEqual(components.calendar?.calendarIdentifier, NSCalendarIdentifierGregorian)

}

Thank you!

nmdias
  • 3,888
  • 5
  • 36
  • 59
  • I'm curious why you are writing your own date formatter instead of just using `NSDateFormatter`? – Daniel T. Jun 11 '16 at 20:08
  • I am, It's a subclass of an NSDateFormatter, actually. I do not know beforehand what the format may be, only that it will conform to 1 of 3 possible RFC specs, and it will, unfortunately, need to play loose with the one above (RFC822). – nmdias Jun 11 '16 at 20:25
  • And I assume the array of strings is just a representative sample? (Otherwise, you could just create an array of NSDates and be done with it.) – Daniel T. Jun 11 '16 at 20:35
  • I wouldn't call it representative just yet. I will integrate with 700+ different date sources. I need to test how it will interpret the string date based on a handfull of different date formats within the spec. And like I said, unfortunately, I also need to play loose with the spec. Just getting an NSDate object from the stringFromDate method doesn't guarantee me that the date was properly understood. – nmdias Jun 11 '16 at 20:51
  • Anyways, I'm just getting started in unit testing. Not much experience yet, and maybe writing a lot of similar functions is actually a perfectly ok way of doing this... – nmdias Jun 11 '16 at 20:54

1 Answers1

1

There are two kinds of tests you need.

1) A test to ensure that a particular dateFormat works properly with a particular format of date. 2) A test to ensure that if two different dateFormats work on a particular format of date, that they are either both the same or your logic will pick the correct one.

So I would envision one test for each dateFormat and one test for each date that could potentially match more than one format.

For these tests, the GWT pattern is fine as far as I can tell. It's more a matter of identifying all the various date formats you will need to use and making sure that the correct date format is used for each date.


As an aside, I wouldn't be comfortable subclassing NSDateFormatter, nor do I see that it's necessary... For the inputs you specify in your question, the code below is all that's necessary:

let dateFormatter1: NSDateFormatter = {
    let result = NSDateFormatter()
    result.dateFormat = "EEE, d MMM yyyy HH:mm:ss Z"
    return result
}()

let dateFormatter2: NSDateFormatter = {
    let result = NSDateFormatter()
    result.dateFormat = "EEE, d MMM yyyy HH:mm Z"
    return result
}()

let dateFormatter3: NSDateFormatter = {
    let result = NSDateFormatter()
    result.dateFormat = "EEE, d MMM yyyy HH:mm:ss z"
    return result
}()

func dateFromString(dateString: String) -> NSDate? {
    return dateFormatter1.dateFromString(dateString) ?? dateFormatter2.dateFromString(dateString) ?? dateFormatter3.dateFromString(dateString)
}

And for the code above, I would expect a total of three tests, one for each date formatter.

Daniel T.
  • 32,821
  • 6
  • 50
  • 72
  • I will follow your advice on the testing part. Just as an aside, I feel you threw me a curve ball when you said you "wouldn't be comfortable subclassing NSDateFormatter". I mean, this is oop. Any specific reason on why you wouldn't? I am working with 3 different specs that amount to about about a dozen or more possible formats. Anyways, thank you Sir. I appreciate the help. – nmdias Jun 12 '16 at 10:11
  • A couple of different reasons... (1) NSDateFormatter wasn't designed to be sub-classed. (2) I expect there isn't any place in your code that accepts an NSDateFormatter as a parameter/property where you would even want to insert your date formatter (and since that's the only reason you should sub-class, no subclassing necessary.) Better is to have a single specific date formatter for each source of dates and use only that date formatter when the date is from that source. That avoids the whole problem of trying to make one date formatter to "format them all." – Daniel T. Jun 12 '16 at 15:18