XCTest fails when calling [NSBundle mainBundle]
Asked Answered
P

1

6

I have some code that calls [NSBundle mainBundle] at some point, mainly to read/set preferences. When I unit test the method, the test fails because the test's mainBundle does not contain the file.

This is a known issue, that Apple won't fix as they consider it is not a bug:

the gist of their reply is that XCTest is working correctly by returning it’s own main bundle instead of the bundle of the test target.

Previously our codebase was using a category override on NSBundle's +mainBundle class method to work around this. But this is dangerous because the behaviour of overriding an existing method in a category is undefined.

If the name of a method declared in a category is the same as a method in the original class, or a method in another category on the same class (or even a superclass), the behavior is undefined as to which method implementation is used at runtime.

Actually Xcode warns you about it:

Category is implementing a method which will also be implemented by its primary class

So, what is the proper method to address the issue?

Proptosis answered 11/3, 2015 at 17:36 Comment(1)
XCTest does not and never has returned the test bundle for +[NSBundle mainBundle]. (Same with OCUnit.) I'm not sure where the idea that it does came from—the linked page is no longer available, so I can't see if it has further references—but so far as I know we have never told anyone otherwise.Clog
P
10

The simplest and cleanest way of fixing this issue is to partially mock the NSBundle class in your unit tests to return [NSBundle bundleForClass:[self class]] when you call [NSBundle mainBundle].

You may put this in your -setup method so that it is mocked for your entire test class:

static id _mockNSBundle;

@implementation MyTests

- (void)setUp
{
  [super setUp];

  _mockNSBundle = [OCMockObject niceMockForClass:[NSBundle class]];
  NSBundle *correctMainBundle = [NSBundle bundleForClass:self.class];
  [[[[_mockNSBundle stub] classMethod] andReturn:correctMainBundle] mainBundle];
}

@end

Nice and clean.

[Source]

Proptosis answered 11/3, 2015 at 17:36 Comment(3)
As mockNSBundle is static, why not use the static +setup method?Shafer
How to do it in the Swift?Electrician
@Electrician Well in Swift you would not use OCMock anyway. Instead, you would need to refactor your code to use code injection and then in your test class you would subclass NSBundle and override the function.Proptosis

© 2022 - 2024 — McMap. All rights reserved.