OCUnit test for protocols/callbacks/delegate in Objective-C

后端 未结 2 1610
伪装坚强ぢ
伪装坚强ぢ 2020-12-07 15:02

Using OCUnit, is there a way to test delegate protocols?

I\'m trying this, which doesn\'t work.

-(void) testSomeObjDelegate {
  SomeObj obj = [[SomeO         


        
相关标签:
2条回答
  • 2020-12-07 15:32

    Testing a delegate is trivial. Just set an ivar in the test in your callback method, and check it after what should be triggering the delegate callback.

    For example, if I have a class Something that uses a delegate of protocol SomethingDelegate and sends that delegate -something:delegateInvoked: in response to some message, I can test it lik ethis:

    @interface TestSomeBehavior : SenTestCase <SomethingDelegate>
    {
        Something *_object;
        BOOL _callbackInvoked;
    }
    @end
    
    @implementation TestSomeBehavior
    
    - (void)setUp {
        [super setUp];
        _object = [[Something alloc] init];
        _object.delegate = self;
    }
    
    - (void)tearDown {
        _object.delegate = nil;
        [_object release];
        [super tearDown];
    }
    
    - (void)testSomeBehaviorCallingBack {
        [_object doSomethingThatShouldCallBack];
    
        STAssertTrue(_callbackInvoked,
                     @"Delegate should send -something:delegateInvoked:");
    }
    
    - (void)something:(Something *)something delegateInvoked:(BOOL)invoked {
        _callbackInvoked = YES;
    }
    
    @end
    

    I think you already understand this, however, from the way you've phrased your question. (I'm mostly posting this for other readers.) I think you're actually asking a more subtle question: How do I test something that may occur later such as something that spins the runloop. My cue is your mention of sleeping and threading.

    First off, you should not just arbitrarily invoke a method on another thread. You should only do so if it's documented to be safe to use in that way. The reason is that you don't know what the internals of the class do. For example, it might schedule events on the run loop, in which case running the method on a different thread will make them happen on a different run loop. This would then screw up the class's internal state.

    If you do need to test something that may take a little time to happen, you can do this just by running the current run loop. Here's how I might rewrite the individual test method above to do that:

    - (void)testSomeBehaviorCallingBack {
        NSDate *fiveSecondsFromNow = [NSDate dateWithTimeIntervalSinceNow:5.0];
    
        [_object doSomethingThatShouldCallBack];
    
        [[NSRunLoop currentRunLoop] runUntilDate:fiveSecondsFromNow];
    
        STAssertTrue(_callbackInvoked,
                     @"Delegate should send -something:delegateInvoked:");
    }
    

    This will spin the current run loop in the default mode for 5 seconds, under the assumption that -doSomethingThatShouldCallBack will schedule its work on the main run loop in the default mode. This is usually OK because APIs that work this way often let you specify a run loop to use as well as a mode to run in. If you can do that, then you can use -[NSRunLoop runMode:beforeDate:] to run the run loop in just that mode instead, making it more likely that the work you're expecting to be done will be.

    0 讨论(0)
  • 2020-12-07 15:52

    Please, review Unit Testing Asynchronous Network Access. I think can help you. In short what it does is:

    Add the following method which will take care of the synchronization between the unit test code and the asynchronous code under test:

    - (BOOL)waitForCompletion:(NSTimeInterval)timeoutSecs {
        NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:timeoutSecs];
    
        do {
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:timeoutDate];
            if([timeoutDate timeIntervalSinceNow] < 0.0)
                break;
        } while (!done);
    
        return done;
    }
    
    0 讨论(0)
提交回复
热议问题