自分の作ったアプリで和暦を使っていなくても、ユーザのカレンダー設定が和暦だったりすると
日付表示がおかしくなることがあります。
カレンダー設定を和暦に変更して実機での動作確認をすればいいのですが、いつもそれを気にして確認などはしないですし忘れがちです。
それに、設定によって挙動が変わってしまうのであれば、日付に関するテストコードを書いても意味がなくなってしまいます。
事前調査
iPhoneの設定でカレンダーを和暦に設定した上で、適当なアプリにNSLog(@"%@", [[NSUserDefaults standardUserDefaults] objectForKey:@"AppleLocale"]);
で記述して起動します。
そうすると、現在選択されているロケールが取得出来ます。
OCMockを使って和暦設定時のロケールを返すMockを作成
OCMockを使い、先ほど取得したロケールを返すようにします。
いろいろ使い回せるように、下のようにSenTestCase
を継承したベースのテストケースを作成し、
和暦設定時のロケールのMockを作成するメソッドを追加します。
#import <SenTestingKit/SenTestingKit.h>
#import <OCMock/OCMock.h>
@interface XXXTestCase : SenTestCase
#pragma mark - NSLocale(ja_JP@calendar=japanese)
/*!
@method createJPLocaleWithJapaneseCalendarMock
@abstract iPhoneのカレンダー設定を和暦に書き換えたMockを作成
@discussion
iPhoneの[設定]-[一般]-[言語環境]-[カレンダー] = 和暦
*/
- (id)createJPLocaleWithJapaneseCalendarMock;
@end
@interface XXXTestCase ()
@property (nonatomic) NSMutableArray *mocksToVerify;
@end
@implementation XXXTestCase
- (void)tearDown
{
for (id mock in self.mocksToVerify) {
[mock verify];
}
self.mocksToVerify = nil;
// [...]
[[NSUserDefaults standardUserDefaults] removePersistentDomainForName:XXXTestDomainName];
[super tearDown];
}
#pragma mark - NSLocale(ja_JP@calendar=japanese)
- (id)createJPLocaleWithJapaneseCalendarMock
{
NSLocale *locale = [NSLocale localeWithLocaleIdentifier:[NSString stringWithFormat:@"ja_JP@calendar=%@",
NSJapaneseCalendar]];
id localeMock = [self autoVerifiedMockForClass:[NSLocale class]];
[[[[localeMock stub] classMethod] andReturn:locale] systemLocale];
[[[[localeMock stub] classMethod] andReturn:locale] currentLocale];
[[[[localeMock stub] classMethod] andReturn:locale] autoupdatingCurrentLocale];
return localeMock;
}
#pragma mark - Setup and verify mock: http://www.objc.io/issue-1/testing-view-controllers.html
- (void)verifyDuringTearDown:(id)mock
{
if (!self.mocksToVerify) {
self.mocksToVerify = @[].mutableCopy;
}
[self.mocksToVerify addObject:mock];
}
- (id)autoVerifiedMockForClass:(Class)aClass
{
id mock = [OCMockObject mockForClass:aClass];
[self verifyDuringTearDown:mock];
return mock;
}
- (id)autoVerifiedPartialMockForObject:(id)object
{
id mock = [OCMockObject partialMockForObject:object];
[self verifyDuringTearDown:mock];
return mock;
}
- (id)autoVerifiedNickMockForClass:(Class)aClass
{
id mock = [OCMockObject niceMockForClass:aClass];
[self verifyDuringTearDown:mock];
return mock;
}
@end
和暦設定時のロケールを返すMockを利用したテストケース
yyyyMM
の日付文字列を受け取って、それを年と月に分割した後、
'April, 2014'のようなMMMM, yyy
書式の文字列を変換する処理があり、
カレンダーが和暦設定だと、なぜか'April, 4002'となってしまうバグがあったとします。
それを確かめるテストコードは下記のようになります。
#import "XXXTestCase.h"
#import <OCMock.h>
@interface XXXDateConvertTests : XXXTestCase
@property (nonatomic) NSString *dateString;
@end
@implementation XXXDateConvertTests
- (void)setUp
{
[super setUp];
self.dateString = @"20140408";
}
- (void)tearDown
{
// Put teardown code here. This method is called after the invocation of each test method in the class.
[super tearDown];
}
// MARK: 日付変換処理だけ抜き出した
- (NSString *)postedAtWithDateFormatter:(NSDateFormatter *)dateFormatter
{
NSInteger year = [self.dateString substringWithRange:NSMakeRange(0, 4)].integerValue;
NSInteger month = [self.dateString substringWithRange:NSMakeRange(4, 2)].integerValue;
[dateFormatter setDateFormat:@"yyyy-MM-dd"];
// MARK: 和暦設定の場合、年を正常にパースできないのでカレンダーをグレゴリオ暦に変更
// dateFormatter.calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDate *date = [dateFormatter dateFromString:[NSString stringWithFormat:@"%i-%i-1", year, month]];
[dateFormatter setLocale:[[NSLocale alloc]initWithLocaleIdentifier:@"en_US"]];
[dateFormatter setDateFormat:@"MMMM, yyyy"];
return [dateFormatter stringFromDate:date];
}
- (void)testPostedAt
{
STAssertFalse([[[NSCalendar currentCalendar] calendarIdentifier] isEqualToString:NSJapaneseCalendar], @"");
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
NSString *resultString = [self postedAtWithDateFormatter:[[NSDateFormatter alloc] init]];
STAssertEqualObjects(resultString, @"April, 2014", @"");
[self createJPLocaleWithJapaneseCalendarMock]; // 和暦設定に変更
NSLog(@"%@", dateFormatter.locale.localeIdentifier);
dateFormatter = [[NSDateFormatter alloc] init];
resultString = [self postedAtWithDateFormatter:dateFormatter];
STAssertFalse([resultString isEqualToString:@"April, 2014"], @""); // April, 4002になってしまう
}
@end