Skip to content

Instantly share code, notes, and snippets.

@samleb
Last active December 17, 2015 06:08
Show Gist options
  • Select an option

  • Save samleb/5562831 to your computer and use it in GitHub Desktop.

Select an option

Save samleb/5562831 to your computer and use it in GitHub Desktop.
Promised, an Objective-C implementation of Promise Unit Tests
//
// 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