tl;dr: jump to the solution
If you are writing unit tests, you have come to a point where some code on your AppDelegate class is executed while running your tests. Then the solution is to replace your AppDelegate class when running tests.
Here’s one way to do it in objective-c :
Note that you would also check if you’re running the unit tests on every method of the class to do an early return — for example when dealing notifications that could interfere with your tests.
In the init method we originally swapped the Storyboard to be loaded with an empty one called UnitTest, but we can go even further and avoid creating an extra file and setting nil instead so no storyboard is even loaded.
Note if you do tests on UI, you might want to create at least a window instead.
What about Swift?
That’s the recurring question everybody asks himself nowadays. So we simply started by translated the code above in Swift:
You would expect that to work but if you run it you will get an error like this:
*** First throw call stack:
0 CoreFoundation 0x000000010b80fc65 __exceptionPreprocess + 165
1 libobjc.A.dylib 0x000000010d82fbb7 objc_exception_throw + 45
2 CoreFoundation 0x000000010b80fb9d +[NSException raise:format:] + 205
3 CoreFoundation 0x000000010b744783 -[__NSCFDictionary setObject:forKey:] + 99
4 ProjectLego 0x000000010b5d8c5f _TFC11ProjectLego11AppDelegatecfMS0_FT_S0_ + 831
5 ProjectLego 0x000000010b5d8d61 _TToFC11ProjectLego11AppDelegatecfMS0_FT_S0_ + 17
6 UIKit 0x000000010c5638bf UIApplicationMain + 1217
7 ProjectLego 0x000000010b5d8eb7 main + 135
8 libdyld.dylib 0x000000010df7e145 start + 1
9 ??? 0x000000000000000a 0x0 + 10
libc++abi.dylib: terminating with uncaught exception of type NSException
And now thinking about this mutating method sent to immutable object exception, you might wonder how it was supposed to work on Objective-C because we change the value of a NSDictionary which is supposed to be immutable.
Apparently, you can use setValue:forKey: to change values of a __NSCFDictionary instance, which is the kind of object you get when you read a plist file.
This is different from a __NSDictionaryI instance where you won’t be able to change the values, only a __NSDictionaryM will work as expected.
Hard to say then where the bug is in Objective-C or Swift, though it is surprising that you can change the Dictionary content in Obj-c.
Anyway to solve the initial problem, we went through two solutions:
- Create an objective C category containing the method to remove the Storyboard
- Add Bridging Header and include the category #import in it
- Call the method in AppDelegate
This does work but involve to create an extra file and bridge to Objective-C which might not be ideal for a Swift project.
The final and preferable solution is to use a make the change in the main file, and that was nice to learn that it was possible to create/use one as Xcode generated project don’t use any main file — handled by the @UIApplicationMain directive in AppDelegate.
- Remove the @UIApplicationMain line from your AppDelegate
- Add a main.swift file like below: