سؤال

Why does the following unit test pass using Xcode 5.0 and XCTesting? I mean, I understand the bottom line: 1 == 0 is not evaluated. But why is it not evaluated? How can I make this perform so that it would fail?

- (void)testAnimationResult
{
    [UIView animateWithDuration:1.5 animations:^{
        // Some animation
    } completion:^(BOOL finished) {
        XCTAssertTrue(1 == 0, @"Error: 1 does not equal 0, of course!");
    }];
}
هل كانت مفيدة؟

المحلول

This will, technically, work. But of course the test will sit and run for 2 seconds. If you have a few thousand tests, this can add up.

More effective is to stub out the UIView static method in a category so that it takes effect immediately. Then include that file in your test target (but not your app target) so that the category is compiled into your tests only. We use:

#import "UIView+Spec.h"

@implementation UIView (Spec)

#pragma mark - Animation
+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion {
    if (animations) {
        animations();
    }
    if (completion) {
        completion(YES);
    }
}

@end

The above simply executes the animation block immediately, and the completion block immediately if it's provided as well.

نصائح أخرى

@dasblinkenlight was on the right track; this is what I did to make it work correctly:

- (void)testAnimationResult
{
    [UIView animateWithDuration:1.5 animations:^{
        // Some animation
    } completion:^(BOOL finished) {
        XCTAssertTrue(1 == 0, @"Error: 1 does not equal 0, of course!");
    }];

    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]];
}

Better approach (right now) is to use expectation.

func testAnimationResult() {
    let exp = expectation(description: "Wait for animation")

    UIView.animate(withDuration: 0.5, animations: {
        // Some animation
    }) { finished in
        XCTAssertTrue(1 == 0)
        exp.fulfill()
    }

    waitForExpectations(timeout: 1.0, handler: nil)
}

The goal here is to convert an asynchronous operation into a synchronous operation. The most general way to do this is with a semaphore.

- (void)testAnimationResult {
    // Create a semaphore object
    dispatch_semaphore_t sem = dispatch_semaphore_create(0);

    [UIView animateWithDuration:1.5 animations:^{
        // Some animation
    } completion:^(BOOL finished) {
       // Signal the operation is complete
       dispatch_semaphore_signal(sem);
    }];

    // Wait for the operation to complete, but not forever
    double delayInSeconds = 5.0;  // How long until it's too long?
    dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
    long timeoutResult = dispatch_semaphore_wait(sem, timeout);

    // Perform any tests (these could also go in the completion block, 
    // but it's usually clearer to put them here.
    XCTAssertTrue(timeoutResult == 0, @"Semaphore timed out without completing.");
    XCTAssertTrue(1 == 0, @"Error: 1 does not equal 0, of course!");
}

If you want to see some examples of this in action, look at RNCryptorTests.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top