简体   繁体   中英

Best Practice for mocking system date and Location in Objective C unit tests

I'm trying to write a class in Objective C using a TDD approach. The class should basically implement this protocol.

@protocol SeasonService <NSObject>

-(t_Season)currentSeason;

@end

t_Season is just an enum for Winter, Spring, Summer and Autumn.

Assume I have a class that implements the above, pseudo code below

@implementation SeasonServiceImpl

    - (t_Season)currentSeason
    {
        // Get Date
        int month = [self currentMonth];

        // Get Latitude
        float latitude = [self currentLatitude];

        // work out the season based on month and latitude 
        // (i.e, Northern or Southern hemisphere)
        return season;
    }
}

I can test the above public method by using a category to expose the currentMonth and currentLatitude methods, and then use OCMock to partial mock. That at least lets me test my implementation of the code to work out season it is based on the date and location.

BUT

1) To me, it seems like a code smell to be using a category extension to effectively make two private methods into public ones. 2) It's not testing whether I am fetching the location properly.

So how would I code this so I can test that I am fetching the current location correctly? I mean, is the best approach here to have a different designated init method which accepts an instance of a CLLocationManager . That could either be a mocked instance, or a real one for when the code is running in production? Also, should the currentSeason method change to something like currentSeasonForDate:NSDate ?

The traditional solution to this sort of problem is Dependency Injection , which helps drive Separation of Concerns . The job of the SeasonService is to determine what season it is based on the latitude and month. Determining/storing the latitude and month should probably should be the responsibility of other classes.

Depending on your system's requirements, this could be achieved either through construction parameters or method parameters. Either way would allow you to mock out a specific month or latitude directly instead of resorting to category tricks.

Constructor injection:

- initWithLatitudeProvider:id<LatitudeProvider> latitudeProvider
          andMonthProvider:id<MonthProvider> monthProvider { 
    // set ivars or properties
}

- (t_Season)currentSeason
{
    // Get Date
    int month = [[self monthProvider] currentMonth];

    // Get Latitude
    float latitude = [[self latitudeProvider] currentLatitude];

    // work out the season based on month and latitude 
    // (i.e, Northern or Southern hemisphere)
    return season;
}

Or method parameter injection:

- (t_Season)currentSeasonAtLatitude:(float)latitude inMonth:(int)month
{
    // work out the season based on month and latitude 
    // (i.e, Northern or Southern hemisphere)
    return season;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM