16

I have an iPhone iOS4.1 application that uses localized strings. I have just started building unit tests using the SenTestingKit. I have been able to successfully test many different types of values.

I am unable to correctly test any of my code that uses NSLocalizedString calls, because when the code runs in my LogicTests target, all of my NSLocalizedString calls only return the string key.

I have added my Localizable.strings file to the LogicTests target.

My question is: How must I configure my LogicTests target so that calls to NSLocalizedString will return the localized string and not the string key.

razlebe
  • 7,134
  • 6
  • 42
  • 57
Jay Haase
  • 1,979
  • 1
  • 16
  • 36

7 Answers7

10

This problem was driving me crazy, but I was able to get NSLocalizedString to behave.

zoul was right, if you print the mainBundle to the console in a logic test, it's not the same bundle that contains your Localizable.strings file. You need to conditionally redefine NSLocalizedString whenever you run your unit tests. I did it in the following steps:

  1. We need a way to tell when we're in our logic tests target, so add something like LOGIC_TESTS to your logic tests target's Preprocessor Macros build setting.
  2. There's only 1 place in my code where I need to redefine NSLocalizedString, so I was able to place the following code in the header corresponding to that class. If you're having this problem in multiple places, I'd suggest putting the following code in a header and #include-ing it where you need it (I tried using a .pch file but it doesn't work in Logic Tests). Anyway, place this somewhere in the header of class(es) that use NSLocalizedString:

    #ifdef LOGIC_TESTS
    #undef NSLocalizedString
    #define NSLocalizedString(key, comment) [[NSBundle bundleWithIdentifier:@"YOUR_IDENTIFIER"] localizedStringForKey:(key) value:@"" table:nil]
    #endif
    

Replace YOUR_IDENTIFIER with the Bundle Identifier of your app's bundle (found in your Info.plist file, key is CFBundleIdentifier). This assumes that you've defined LOGIC_TESTS as a preprocessor macro only in your Logic Tests target.

edit: Curiously, once I removed some debugging code this solution stopped working. It looks like you have to trick Xcode into loading the bundle as well. The following does it:

NSString *path = @"path_to_main_bundle";
NSBundle *bundle = [NSBundle bundleWithPath:path];
NSLog(@"bundles: %@", [NSBundle allBundles]);

Where path_to_main_bundle is == [[NSBundle mainBundle] bundlePath] when you run your main target. Just log it once in gdb or using NSLog on your app delegate to grab the path. It should look something like /Users/YOUR_USER_NAME/Library/Application Support/iPhone Simulator/4.1/Applications/UUID_LOTS_OF_LETTERS_AND_NUMBERS_HERE/App.app.

I placed that code in the setUp call for one of my logic test classes. And no, I have no idea why I have to log allBundles to make it work, so anyone who has a clue, please let me know!

Mikhail Grebionkin
  • 3,806
  • 2
  • 17
  • 18
kevboh
  • 5,207
  • 5
  • 38
  • 54
  • There has to be a more graceful way to do this, but I have other stuff I need to work on. I suspect the real fix is finding a way to locate the bundle resources without weird hardcoding and introspection. – kevboh Oct 11 '10 at 19:19
  • Dude! Thanks for a such a detailed answer. This will be a big help. I am surprised so few people seem to have encountered this -- maybe not many people that localize unit test. ;-) – Jay Haase Oct 12 '10 at 13:34
  • No problem. Yeah, it's weird how I couldn't find anything on this problem in the interwebs. Definitely let me know if you figure anything else out on this. – kevboh Oct 12 '10 at 16:24
  • Does not work for me. The hardcoded path does not seem to be permanent. – Sunkas Feb 10 '16 at 13:25
7

I was able to use NSLocalizedString using the following code in the setup for my unit test

- (void)setUp
{
    [super setUp];

    NSString *bundlePath = [[NSBundle bundleForClass:[self class]] resourcePath];
    [NSBundle bundleWithPath:bundlePath];
}
cpungaliya
  • 228
  • 3
  • 7
  • This worked great for me, thanks for sharing! Using NSLocalizedStrings seems to automatically work when the unit tests load the main application bundle (bundle loader setting). – Eneko Alonso Dec 30 '12 at 22:51
4

I encounter the same problem, and, thanks @kevboth, I tackle it by adding two lines to YourUnitTests-Prefix.pch:

#undef NSLocalizedString
#define NSLocalizedString(key, comment) [[NSBundle bundleForClass:[self class]] localizedStringForKey:(key) value:@"" table:nil]
smilingpoplar
  • 1,065
  • 1
  • 15
  • 26
  • Worth noting: #undef NSLocalizedStringWithDefaultValue #define NSLocalizedStringWithDefaultValue(key, tbl, bundle, val, comment) [[NSBundle bundleForClass:[self class]] localizedStringForKey:(key) value:(val) table:(tbl)] – Raphael Oliveira Mar 19 '13 at 15:38
2

A shortcut for Swift: This is a simple version which can be extended to different use cases (e.g. with the use of tableNames).

public func NSLocalizedString(key: String, referenceClass:AnyClass) -> String 
{
    let bundle = NSBundle(forClass: referenceClass)
    return NSLocalizedString(key, tableName:nil, bundle: bundle, comment: "")
}

This global method can be placed in a single .swift file or somewhere else outside the class scope. Use it like this:

NSLocalizedString("YOUR-KEY", referenceClass: self)
GatoCurioso
  • 225
  • 3
  • 11
1

Maybe NSLocalizedString will only work inside the application tests? This is a macro that invokes localizedStringForKey:value:table: on the main bundle. Maybe +[NSBundle mainBundle] returns something iffy in the testing target?

zoul
  • 102,279
  • 44
  • 260
  • 354
  • I think you are one to something, I am pretty sure the logic tests use a different bundle then the main bundle. – Jay Haase Oct 11 '10 at 14:42
0

The cleanest solution seems to me just to include a Localizable.strings in your octest bundle.

Cris
  • 1,939
  • 3
  • 23
  • 37
0

Swift 4.1 - Xcode 9.3

Localizable.strings (en)

"TEXT_TO_LOCALIZE" = "Hello!";

Localizable.strings (es)

"TEXT_TO_LOCALIZE" = "Hola!";

XCTestCase

class AppTestCase: XCTestCase {
   func testLocalize() {
      let localizedText = "TEXT_TO_LOCALIZE".localized(for: reference)
      print(localizedText) // "Hello!" or "Hola!"
   }
}

extension String {
   public func localized(for aClass: AnyClass) -> String {
      let bundle = Bundle(for: aClass)
      return NSLocalizedString(self, tableName: nil, bundle: bundle, comment: "")
   }
}

extension XCTestCase {
   var reference: AnyClass {
      return type(of: self)
   }
} 
juliancadi
  • 987
  • 1
  • 8
  • 23