Last active
December 17, 2015 06:08
-
-
Save samleb/5562831 to your computer and use it in GitHub Desktop.
Promised, an Objective-C implementation of Promise
Unit Tests
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // | |
| // PromisedTests.m | |
| // PromisedTests | |
| // | |
| // Created by Samuel Lebeau on 02/05/13. | |
| // Copyright (c) 2013 Samuel Lebeau. All rights reserved. | |
| // | |
| #import "PromisedTests.h" | |
| #import "PMDeferred.h" | |
| static NSError *kExpectedError; | |
| static NSError *kExpectedError2; | |
| static NSError *kExpectedTimeoutError; | |
| @implementation PromisedTests { | |
| PMDeferred *deferred; | |
| PMPromise *promise; | |
| } | |
| + (void)setUp { | |
| kExpectedError = [NSError errorWithDomain:@"foo" code:42 userInfo:nil]; | |
| kExpectedError2 = [NSError errorWithDomain:@"bar" code:43 userInfo:nil]; | |
| kExpectedTimeoutError = [NSError errorWithDomain:PMPromiseErrorDomain code:PMPromiseTimeoutError userInfo:nil]; | |
| } | |
| - (void)setUp { | |
| [super setUp]; | |
| deferred = [PMDeferred defer]; | |
| promise = deferred.promise; | |
| } | |
| - (void)tearDown { | |
| // Tear-down code here. | |
| [super tearDown]; | |
| } | |
| - (void)testDeferredPromise { | |
| STAssertTrue([promise isKindOfClass:[PMPromise class]], @"Deferred promise should be a PMPromise instance"); | |
| STAssertTrue(promise.isPending, @"Promise should initially be pending"); | |
| STAssertNil(promise.value, @"Promise value should initially be nil"); | |
| STAssertNil(promise.error, @"Promise error should initially be nil"); | |
| PMPromise *promise2 = deferred.promise; | |
| STAssertEquals(promise, promise2, @"Deferred promise should always return the same object"); | |
| } | |
| - (void)testPromiseUserInfo { | |
| NSDictionary *userInfo = @{@"foo": @"bar"}; | |
| deferred = [PMDeferred deferWithUserInfo:userInfo]; | |
| STAssertEqualObjects(deferred.userInfo, userInfo, @"Deferred should have a userInfo dictionary"); | |
| STAssertEqualObjects(deferred.promise.userInfo, userInfo, @"Promise should have the same userInfo"); | |
| } | |
| - (void)testDeferredResolveBeforeSettingResolveBlock { | |
| [self setupResolveBlockBefore:^{ | |
| [deferred resolveWithValue:@"foo"]; | |
| [deferred resolveWithValue:@"bar"]; | |
| [deferred rejectWithError:kExpectedError]; | |
| } after:nil]; | |
| } | |
| - (void)testDeferredResolveAfterSettingResolveBlock { | |
| [self setupResolveBlockBefore:nil after:^{ | |
| [deferred resolveWithValue:@"foo"]; | |
| [deferred resolveWithValue:@"bar"]; | |
| [deferred rejectWithError:kExpectedError]; | |
| }]; | |
| } | |
| - (void)setupResolveBlockBefore:(void(^)())before after:(void(^)())after { | |
| __block NSUInteger resolveBlockCallsCount = 0; | |
| if (before) before(); | |
| [promise callWhenResolved:^(id value) { | |
| resolveBlockCallsCount++; | |
| STAssertEqualObjects(value, @"foo", @"Resolve block should be called with the resolved value"); | |
| } rejected:^(NSError *error) { | |
| STFail(@"Reject block should not be called"); | |
| }]; | |
| PMPromise *promise2 = [promise promiseByCallingBlockWhenResolved:^id(NSString *value) { | |
| STAssertEqualObjects(value, @"foo", @"Resolve block should be called with the resolved value"); | |
| return [value stringByAppendingString:@" bar!"]; | |
| }]; | |
| if (after) after(); | |
| STAssertTrue(promise.isResolved, @"Promise should be resolved"); | |
| STAssertEqualObjects(promise.value, @"foo", @"Promise value should be the resolved value"); | |
| STAssertNil(promise.error, @"Promise error should be nil"); | |
| STAssertTrue(resolveBlockCallsCount == 1, @"Resolve block should only be called once"); | |
| STAssertEqualObjects(promise2.value, @"foo bar!", @"Promise should have the value returned by the PMPromiseResolveValueBlock"); | |
| } | |
| - (void)testDeferredRejectBeforeSettingRejectBlock { | |
| [self setupRejectBlockBefore:^{ | |
| [deferred rejectWithError:kExpectedError]; | |
| [deferred rejectWithError:kExpectedError2]; | |
| [deferred resolveWithValue:@"foo"]; | |
| } after:nil]; | |
| } | |
| - (void)testDeferredRejectAfterSettingRejectBlock { | |
| [self setupRejectBlockBefore:nil after:^{ | |
| [deferred rejectWithError:kExpectedError]; | |
| [deferred rejectWithError:kExpectedError2]; | |
| [deferred resolveWithValue:@"foo"]; | |
| }]; | |
| } | |
| - (void)setupRejectBlockBefore:(void(^)())before after:(void(^)())after { | |
| __block NSUInteger rejectBlockCallsCount = 0; | |
| if (before) before(); | |
| [promise callWhenResolved:^(id value) { | |
| STFail(@"Resolve block should not be called"); | |
| } rejected:^(NSError *error) { | |
| rejectBlockCallsCount++; | |
| STAssertEqualObjects(error, kExpectedError, @"Reject block should be called with the error of rejection"); | |
| }]; | |
| if (after) after(); | |
| STAssertTrue(promise.isRejected, @"Promise should be rejected"); | |
| STAssertEqualObjects(promise.error, kExpectedError, @"Promise error should be the error of rejection"); | |
| STAssertNil(promise.value, @"Promise value should be nil"); | |
| STAssertTrue(rejectBlockCallsCount == 1, @"Reject block should only be called once"); | |
| } | |
| - (void)testPromiseCancel { | |
| __block NSUInteger cancelBlockCallsCount = 0; | |
| deferred.cancelBlock = ^() { | |
| cancelBlockCallsCount++; | |
| }; | |
| [promise cancel]; | |
| STAssertTrue(promise.isCancelled, @"promise should be cancelled"); | |
| STAssertTrue(cancelBlockCallsCount == 1, @"Deferred cancel block should be called when promise is cancelled"); | |
| [promise cancel]; | |
| STAssertTrue(cancelBlockCallsCount == 1, @"Deferred cancel block should not be called twice when promise is cancelled twice"); | |
| deferred = [PMDeferred defer]; | |
| STAssertNoThrow([deferred.promise cancel], @"Cancelling a promise with no deferred cancel block should not throw exception"); | |
| deferred = [PMDeferred defer]; | |
| promise = deferred.promise; | |
| [deferred resolveWithValue:@42]; | |
| [promise cancel]; | |
| STAssertTrue(promise.isResolved, @"Cancelling a resolved promise should not change its state"); | |
| deferred = [PMDeferred defer]; | |
| promise = deferred.promise; | |
| [deferred rejectWithError:kExpectedError]; | |
| [promise cancel]; | |
| STAssertTrue(promise.isRejected, @"Cancelling a resolved promise should not change its state"); | |
| } | |
| - (void)testPromiseWithTimeout { | |
| PMPromise *timeoutPromise = [promise promiseWithTimeout:0.050f]; | |
| STAssertFalse(promise == timeoutPromise, @"Promise should be different from the original promise"); | |
| __block NSError *blockExpectedError; | |
| [timeoutPromise callWhenResolved:^(id value) { | |
| STFail(@"Resolve block should not be called"); | |
| } rejected:^(NSError *error) { | |
| blockExpectedError = error; | |
| }]; | |
| [self wait:0.100f]; | |
| STAssertTrue(timeoutPromise.isRejected, @"Promise should be rejected"); | |
| STAssertEqualObjects(timeoutPromise.error, kExpectedTimeoutError, @"Promise error should be a timeout error"); | |
| STAssertTrue(promise.isPending, @"Original promise should be pending"); | |
| timeoutPromise = [promise promiseWithTimeout:0.100f]; | |
| [self wait:0.020f]; | |
| [deferred resolveWithValue:@42]; | |
| STAssertTrue(timeoutPromise.isResolved, @"Promise should be resolved"); | |
| STAssertEqualObjects(timeoutPromise.value, @42, @"Resolve value should be the resolved value"); | |
| } | |
| - (void)wait:(NSTimeInterval)delay { | |
| NSDate *date = [NSDate date]; | |
| while ([[NSDate date] timeIntervalSinceDate:date] < delay) { | |
| [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode | |
| beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.010f]]; | |
| } | |
| } | |
| - (void)testPromiseOnQueue { | |
| dispatch_queue_t queue = dispatch_queue_create("com.github.samleb.promised-test", DISPATCH_QUEUE_SERIAL); | |
| PMPromise *queuePromise = [promise promiseOnQueue:queue]; | |
| __block dispatch_queue_t blockQueue; | |
| [queuePromise callWhenResolved:^(id value) { | |
| blockQueue = dispatch_get_current_queue(); | |
| }]; | |
| [deferred resolveWithValue:@42]; | |
| STAssertFalse(queuePromise.isResolved, @"Promise should not yet be resolved"); | |
| // Force the queue to process the block | |
| dispatch_sync(queue, ^{ }); | |
| dispatch_release(queue); | |
| STAssertEquals(blockQueue, queue, @"Resolve block should be executed on queue"); | |
| STAssertTrue(queuePromise.isResolved, @"Promise should be resolved"); | |
| STAssertEqualObjects(queuePromise.value, @42, @"Resolve value should be the resolved value"); | |
| } | |
| - (void)testArrayPromiseWithPromises { | |
| PMDeferred *d1 = [PMDeferred defer]; | |
| PMDeferred *d2 = [PMDeferred defer]; | |
| PMPromise *arrayPromise = [PMDeferred arrayPromiseWithPromises:@[d1.promise, d2.promise]]; | |
| STAssertTrue([arrayPromise isKindOfClass:[PMPromise class]], @"Promise should be an instance of PMPromise"); | |
| STAssertTrue(arrayPromise.isPending, @"Promise should be pending"); | |
| } | |
| - (void)testArrayPromiseWithPromisesResolved { | |
| PMDeferred *d1 = [PMDeferred defer]; | |
| PMDeferred *d2 = [PMDeferred defer]; | |
| PMDeferred *d3 = [PMDeferred defer]; | |
| PMPromise *arrayPromise = [PMDeferred arrayPromiseWithPromises:@[d1.promise, d2.promise, d3.promise]]; | |
| NSArray *expected = @[@"d1", @"d2", @"d3"]; | |
| __block NSUInteger resolveBlockCallsCount = 0; | |
| [arrayPromise callWhenResolved:^(id value) { | |
| resolveBlockCallsCount++; | |
| STAssertEqualObjects(value, expected, @"Resolve block should be called with the array of promises' values"); | |
| } rejected:^(NSError *error) { | |
| STFail(@"Reject block should not be called"); | |
| }]; | |
| [d2 resolveWithValue:@"d2"]; | |
| STAssertTrue(arrayPromise.isPending, @"Promise should be pending"); | |
| [d3 resolveWithValue:@"d3"]; | |
| STAssertTrue(arrayPromise.isPending, @"Promise should be pending"); | |
| [d1 resolveWithValue:@"d1"]; | |
| STAssertTrue(arrayPromise.isResolved, @"Promise should be resolved"); | |
| STAssertEqualObjects(arrayPromise.value, expected, @"Promise value should be the array of promises' values"); | |
| STAssertTrue(resolveBlockCallsCount == 1, @"Resolve block should only be called once"); | |
| } | |
| // FIXME: This test currently needs to be profiled with the Memory Leaks template in Instruments | |
| // (by attaching to the running otest process) | |
| - (void)testArrayPromiseWithPromisesPendingShouldNotLeak { | |
| PMDeferred *d1 = [PMDeferred defer]; | |
| PMDeferred *d2 = [PMDeferred defer]; | |
| PMPromise *arrayPromise = [PMDeferred arrayPromiseWithPromises:@[d1.promise, d2.promise]]; | |
| [arrayPromise callWhenResolved:^(id value) { | |
| STFail(@"Resolve block should not be called"); | |
| } rejected:^(NSError *error) { | |
| STFail(@"Reject block should not be called"); | |
| }]; | |
| [d1 resolveWithValue:@"d1"]; | |
| STAssertTrue(arrayPromise.isPending, @"Promise should still be pending"); | |
| STAssertNil(arrayPromise.value, @"Promise value should be nil"); | |
| } | |
| - (void)testArrayPromiseWithPromisesRejected { | |
| PMDeferred *d1 = [PMDeferred defer]; | |
| PMDeferred *d2 = [PMDeferred defer]; | |
| PMDeferred *d3 = [PMDeferred defer]; | |
| PMDeferred *d4 = [PMDeferred defer]; | |
| PMPromise *arrayPromise = [PMDeferred arrayPromiseWithPromises:@[d1.promise, d2.promise, d3.promise, d4.promise]]; | |
| NSError *expected = kExpectedError; | |
| __block NSUInteger rejectBlockCallsCount = 0; | |
| [arrayPromise callWhenResolved:^(id value) { | |
| STFail(@"Resolve block should not be called"); | |
| } rejected:^(NSError *error) { | |
| rejectBlockCallsCount++; | |
| STAssertEqualObjects(error, expected, @"Reject block should be called with the error of rejection of the first rejected promise"); | |
| }]; | |
| [d2 resolveWithValue:@"d2"]; | |
| STAssertTrue(arrayPromise.isPending, @"Promise should be pending"); | |
| [d3 rejectWithError:kExpectedError]; | |
| STAssertTrue(arrayPromise.isRejected, @"Promise should be rejected"); | |
| [d1 rejectWithError:kExpectedError2]; | |
| [d4 resolveWithValue:@"d4"]; | |
| STAssertTrue(arrayPromise.isRejected, @"Promise should still be rejected"); | |
| STAssertNil(arrayPromise.value, @"Promise value should be nil"); | |
| STAssertEqualObjects(arrayPromise.error, kExpectedError, @"Promise error should be the error of rejection of the first rejected promise"); | |
| STAssertTrue(rejectBlockCallsCount == 1, @"Resolve block should only be called once"); | |
| } | |
| @end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment