In Objective-C++, for testing, how can I make a class nil at runtime?

Viewed 92

Here's the situation:

In async functions, I capture a weakSelf.

__block auto weakSelf = self;

Then, inside the block, I capture a strongSelf of that weakSelf.

[_someIvar someAsyncMethod:^{
   __strong auto strongSelf = weakSelf;
}];

But, if strongSelf is nil, I do some error handling and reporting.

if (!strongSelf) {
   _NotifyDelegate(someDeallocationError); // C
}

The whole thing:

__block auto weakSelf = self;
[_someIvar someAsyncMethod:^{
   __strong auto strongSelf = weakSelf;
   if (!strongSelf) {
      // How can I trigger this line?
      _NotifyDelegate(someDeallocationError); // C
   }
}
}];

This is all legacy code, and I'm adding unit tests using OCMock. How can I make strongSelf nil at runtime and trigger that delegate notification?

1 Answers

You haven't specified any class names as far as I can see, but assuming you have:

@interface SomeClass

…

@end

…

@interface IvarClass
- (void) someAsyncMethod:(void(^)(void))block;
@end

…

@implementation SomeClass
{
    IvarClass* _someIvar;
}
…

- (void)someMethod
{
  __block auto weakSelf = self;
  [_someIvar someAsyncMethod:^{
    __strong auto strongSelf = weakSelf;
    if (!strongSelf) {
      // How can I trigger this line?
      _NotifyDelegate(someDeallocationError); // C
    }
  }];
}

@end

In that case, I would subclass IvarClass with a StubIvarClassDelayedAsync where you override - (void) someAsyncMethod: so that the block is simply stored in an ivar. You then implement another method on it along the lines of - (void)completeAsyncMethod which calls the block.

You will need to use an instance of this StubIvarClassDelayedAsync as your _someIvar in your test - either you can inject this directly via the existing API on SomeClass, otherwise you may may need to subclass SomeClass to change wherever the production instance is created.

Putting it all together in the test method: Within an @autoreleasepool block, create an instance of your SomeClass (or its stub subclass), prepare it with an instance of StubIvarClassDelayedAsync, then call someMethod. This in turn should cause your overridden version of someAsyncMethod: to be called, which stores the block reference. (You can assert that the method was called for good measure.) nil out the SomeClass pointer in your test and close the pool block to remove any last references. You may want to assert that dealloc is called on your SomeClass instance, which you can easily do if using a dummy subclass. Make sure to retain a pointer to your StubIvarClassDelayedAsync instance however. Finally, call completeAsyncMethod on that stub instance, which should cause the block to take the error handling branch.

I'd be more specific about a code example if you provided a little more than just unnamed fragments, if you've got code preceding or following these fragments within the respective methods, then that may need to be taken into account or refactored first. Real names would also be useful.

Related