While developing a set of date calculations and language rules for converting numeric values and dates to strings, I'm writing tests that assert the outcome of the string formatting method. An imaginary assertion for it might looks like this:
NSAssert([dateString isEqualToString:@"Three days, until 6:00 PM"], @"Date string should match expectation");
However, because the app is localized for several languages, and my fellow developers are also from and in different locales than I, it can happen that your device or simulator is set to a different locale than the one that the tests are being written for. In a scenario like this, the contents of the dateString
might be something like:
@"Drie dagen, tot 18:00" // the assertion fails
@"Drei Tage, bis 18 Uhr" // the assertion also fails
This may or may not be the correct date notation for these locales, but the part that my question is about, is how to be able to run tests to a specific locale, when the underlying code makes use of Apple API like this:
[NSDateFormatter localizedStringFromDate:date
dateStyle:NSDateFormatterNoStyle
timeStyle:NSDateFormatterShortStyle];
I would love to cover at two or more languages in my assertions, with something like this:
[NSSomething actionToSetTheLocaleTo:@"en_US"];
dateString = ...; // the formatting
NSAssert([dateString isEqualToString:@"Three days, until 6:00 PM"], @"match en_US");
[NSSomething actionToSetTheLocaleTo:@"nl_NL"];
dateString = ...; // the formatting
NSAssert([dateString isEqualToString:@"Drie dagen, tot 18:00"], @"match nl_NL");
Who knows how to achieve this effect?
Notes:
Links on the topic:
@Desmond pointed out a working solution. Until he places an answer in here to put this info in, let me summarize what I ended up doing with a bit of code.
The solution, turns out, is "as easy" as swizzling the methods that the class methods use internally:
beforeEach(^{
[NSBundle ttt_overrideLanguage:@"nl"];
[NSLocale ttt_overrideRuntimeLocale:[NSLocale localeWithLocaleIdentifier:@"nl_NL"]];
});
afterEach(^{
[NSLocale ttt_resetRuntimeLocale];
[NSBundle ttt_resetLanguage];
});
The ttt_...
methods you see above use the categories on NSObject, NSLocale and NSBundle to check at runtime whether it should use the original methods or return something else. This method works flawlessly when writing your tests, and although it doesn't technically use any private API, I would strongly suggest only to use this in your test setup, not for anything you submit to the App Store for review.
In this gist you'll find the Objective-C categories I added to my app's test target to achieve the required behavior.
Since this is in a unit test, have you tried setting the locale on the NSDateFormatter
? You should not have to set the locale on the whole simulator, you can make it a parameter of your tests. Most cocoa methods that are dependent on locale can take it as a parameter or property.
Something like:
NSLocale *locale = [NSLocale localeWithLocaleIdentifier:@"en-US"];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setLocale:locale];
[formatter setDateStyle: NSDateFormatterNoStyle];
[formatter setTimeStyle: NSDateFormatterShortStyle];
[formatter setFormatterBehavior:NSDateFormatterBehavior10_4];
thing = [formatter stringForObjectValue:date];
The localizedStringFromDate:dateStyle:timeStyle: method you're using is documented here, and it's pretty explicit about everything but the locale. So you can do the same steps, but set the locale to something other than the system's current locale using the steps outlined above.
The only way I see is what quellish has mentioned. However, you mentioned it exists in a lot of places.
Instead of rewriting all your current code, in your pch you could do something fancy like
#import "UnitTestDateFormatter.h"
#define NSDateFormatter UnitTestDateFormatter
And then simply create a subclass to handle it:
@implementation UnitTestDateFormatter
- (id) init
{
self = [super init];
if(self != nil)
{
[self setLocale:...];
}
return self;
}
@end
At least then your code can remain unchanged.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With