开发者

How to mock property/internal value of UIApplication?

开发者 https://www.devze.com 2023-01-08 03:28 出处:网络
I\'m writing unit tests. And I cannot test one function, because it calls keyWindow UIWindow* window = [UIApplication sharedApplication].keyWindow;

I'm writing unit tests. And I cannot test one function, because it calls keyWindow

UIWindow* window = [UIApplication sharedApplication].keyWindow;

And keyWindow returns nil (I don't have any window开发者_如何学Go). But I need to return anything, but nil.

I used category to manually set keyWindow value, but this didn't work

@interface UIApplication(UnitTest)
- (id)getKeyWindow;
@end

@implementation UIApplication(UnitTest)
- (id)getKeyWindow
{
    return [self keyWindow];
}
@end

// compiler error: lvalue required as left operand of assignment
[[UIApplication sharedApplication] getKeyWindow] = [[UIWindow alloc] init]; 

What would you do in my place?


Apple doesn't make it easy to test your code when you start getting close to the framework classes. I've found UIApplication particularly difficult due to its singleton nature, and the fact that the framework creates the instance for you during setup. Solutions to problems like this usually depend on how you're testing; for example, do you have a valid UIApplication object in your tests? If you're using OCUnit then [UIApplication sharedApplication] may itself return nil, unless you've set up your test target to run in your application bundle (I've never managed to get this to work, honestly).

One option is to simply not test this. Most of the time your interactions with the main window are relatively simple, and tests of this code are testing the UIKit framework as much as your own code. This is a stylistic decision that depends on how you've structured your code, how comfortable you are leaving this small area untested (or, more appropriately, un-test-automated), and how difficult it will be to write the tests.

If you have code that relates to the UIWindow object that you decide you do need to test, I would suggest you encapsulate the functionality in a way that you can then control under test. You could do this by creating a subclass of UIApplication for your app which returns the UIWindow object from a custom method. Use this method in your code, rather than accessing the window property directly, and in your tests override this method to return whatever you like.


I was able to mock the delegate object and verify certain expectations such as applicationDidBecomeActive by swizzling the application delegate setter.

id<UIApplicationDelegate> delegate = [MyCustomDelegate alloc] init];
UIApplication *app = [UIApplication alloc] init];
app.delegate = delegate;
//results in "There can only be one UIApplication" error

Instead:

@implementation AppDelegateTests {

- (void)testAppDelegate {
     //perform swizzle on [UIApplication class] and method @selector(setDelegate:) with
     //[self class] and at the bottom @selector(swizzledSetDelegate:)

     id<UIApplicationDelegate> delegate = [MyCustomDelegate alloc] init];

     //Here's the magic, this line actually calls [UIApplication setDelegate] and not the swizzledSetDelegate method below. 
     //If you don't understand this, look up how swizzling works and hopefully you can wrap your head around it. The logic is quite mind-bendy.
     [self swizzledSetDelegate:delegate];

     //here set up your mock on the delegate and verify state of things
}

- (void)swizzledSetDelegate:(id<UIApplicationDelegate>)delegate {
     //do nothing
}

}

Note this example is quite custom to what I needed to test, you'll need to think about what you want to mock and if its even possible.


You can create the new window object:

UIWindow* window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

0

精彩评论

暂无评论...
验证码 换一张
取 消