Question

What is the best practice for testing NSDateFormatter methods? For example, lets say I have a method:

- (NSString *)formatStringFromDate:(NSDate *)date {
    NSDateFormatter *f = [[NSDateFormatter alloc] init];
    [f setTimeStyle:NSDateFormatterShortStyle];
    [f setDateStyle:NSDateFormatterNoStyle];

    return [f stringFromDate:date];
}

There are two ways I can think of testing this method using Kiwi:

1) Create the same formatter in the unit test:

it(@"should format a date", ^{
    NSDate *date = [NSDate date];
    NSDateFormatter *f = [[NSDateFormatter alloc] init];
    [f setTimeStyle:NSDateFormatterShortStyle];
    [f setDateStyle:NSDateFormatterNoStyle];

    [[[testObject formatStringFromDate:date] should] equal:[f stringFromDate:date]];
});

2) Explicitly write the intended output:

it(@"should format a date", ^{
    NSDate *date = [NSDate dateWithTimeIntervalSince1970:1385546122];
    NSDateFormatter *f = [[NSDateFormatter alloc] init];
    [f setTimeStyle:NSDateFormatterShortStyle];
    [f setDateStyle:NSDateFormatterNoStyle];

    [[[testObject formatStringFromDate:date] should] equal:@"9:55 am"];
});

Now, to me, #1 seems a bit redundant. I know the test will pass as I'm essentially duplicating the method in my unit test.

Method #2 is a non-starter, as it is incredibly fragile. It completely relies on the test devices current locale being what you'd expect.

So my question is: is there a more appropriate method to test this method, or should I just go ahead with test method #1.

Was it helpful?

Solution

As you say in your comment, what you want to test is that the cell's detailTextLabel's text is set to date with the expected format when a date is present.

This can be tested in several different ways, I expose here the one I would go for.

First of all, creating a date formatter each time we want to format a date is not efficient and it makes it more difficult to test.

So what I suggest is to create a date formatter property in your table view controller:

// .h
@property (strong, nonatomic) NSDateFormatter *dateFormatter;

// .m
- (NSDateFormatter *) dateFormatter
{
    if (_dateFormatter == nil)
    {
        _dateFormatter = [[NSDateFormatter alloc] init];
        [_dateFormatter setTimeStyle:NSDateFormatterShortStyle];
        [_dateFormatter setDateStyle:NSDateFormatterNoStyle];

    }
    return _dateFormatter;
}

For the date formatter I would create a simple test that verifies the timeStyle and dateStyle. I am not familiar with Kiwi so I use OCUnit assertions:

TableViewController *sut;// Instantiate the table view controller
STAssertTrue(sut.dateFormatter.timeStyle == NSDateFormatterShortStyle, nil);
STAssertTrue(sut.dateFormatter.dateStyle == setDateStyle:NSDateFormatterNoStyle, nil);

After having this, the idea is to create a test that verifies that the cell returned by tableView:cellForRowAtIndexPath: has its detailTextLabel's text set with the string returned by the formatter. As you said testing the string returned by the date formatter is fragile so we can mock it, stub stringFromDate: returning a constant and verify that the detailTextLabel's text is set to that constant.

So we write the test. I'd try to write it with Kiwi mocks, so sorry if I do something wrong - the idea is the important thing:

TableViewController *sut;// Instantiate the table view controller

id mockDateFormatter = [NSDateFormatter mock];

NSString * const kFormattedDate = @"formattedDate";
NSDate * const date = [NSDate date];

[mockDateFormatter stub:@selector(stringFromDate:) andReturn:kFormattedDate withArguments:date,nil];

sut.dateFormatter = mockDateFormatter;
sut.dates = @[date];// As an example we have an array of dates to show. In the real case we would have an array of the objects you want to show in the table view.

[sut view];// In case we have the registered cells...
UITableViewCell *cell = [sut tableView:sut.tableView 
                 cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];

STAssertEqualObjects(cell.detailTextLabel.text, kFormattedDate, nil);

And the method to satisfy that test would be something like this:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
{
    // I assume you have a standard cell registered in your table view
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"CellIdentifier"];

    NSDate *date = [self.dates objectAtIndex:indexPath.row];
    cell.detailTextLabel.text = [self.dateFormatter stringFromDate:date];

    return cell;
}

Hope it helps.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top