Skip to content

Commit

Permalink
GoogleUtilities: NSURLSession promise extension (#6753)
Browse files Browse the repository at this point in the history
* GoogleUtilities: NSURLSession promise extension

* Imports fix

* Changelog

* API and API docs

* style

* Changelog fix
  • Loading branch information
maksymmalyhin authored Oct 16, 2020
1 parent 9ea877d commit 7e70a6d
Show file tree
Hide file tree
Showing 9 changed files with 352 additions and 2 deletions.
5 changes: 4 additions & 1 deletion GoogleUtilities.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,10 @@ other Google CocoaPods. They're not intended for direct public usage.
s.test_spec 'unit' do |unit_tests|
# All tests require arc except Tests/Network/third_party/GTMHTTPServer.m
unit_tests.platforms = {:ios => '8.0', :osx => '10.11', :tvos => '10.0'}
unit_tests.source_files = 'GoogleUtilities/Tests/Unit/**/*.[mh]'
unit_tests.source_files = [
'GoogleUtilities/Tests/Unit/**/*.[mh]',
'SharedTestUtilities/URLSession/*.[mh]',
]
unit_tests.requires_arc = 'GoogleUtilities/Tests/Unit/*/*.[mh]'
unit_tests.requires_app_host = true
unit_tests.dependency 'OCMock'
Expand Down
5 changes: 4 additions & 1 deletion GoogleUtilities/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# 7.1.0 -- Unreleased
- Added `NSURLSession` promise extension. (#6753)

# 7.0.0
- All APIs are now public. All CocoaPods private headers are transitioned to public. Note that
- GoogleUtilities may have frequent breaking changes than Firebase. (#6588)
GoogleUtilities may have more frequent breaking changes than Firebase. (#6588)
- Fixed writing heartbeat to disk on tvOS devices. (#6658)
- Refactor `GULSwizzledObject` to ARC to unblock SwiftPM support. (#5862)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

/** The class represents HTTP response received from `NSURLSession`. */
@interface GULURLSessionDataResponse : NSObject

@property(nonatomic, readonly) NSHTTPURLResponse *HTTPResponse;
@property(nonatomic, nullable, readonly) NSData *HTTPBody;

- (instancetype)initWithResponse:(NSHTTPURLResponse *)response HTTPBody:(nullable NSData *)body;

@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#import "GoogleUtilities/Environment/URLSessionPromiseWrapper/GULURLSessionDataResponse.h"

@implementation GULURLSessionDataResponse

- (instancetype)initWithResponse:(NSHTTPURLResponse *)response HTTPBody:(NSData *)body {
self = [super init];
if (self) {
_HTTPResponse = response;
_HTTPBody = body;
}
return self;
}

@end
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#import <Foundation/Foundation.h>

@class FBLPromise<Value>;
@class GULURLSessionDataResponse;

NS_ASSUME_NONNULL_BEGIN

/** Promise based API for `NSURLSession`. */
@interface NSURLSession (GULPromises)

/** Creates a promise wrapping `-[NSURLSession dataTaskWithRequest:completionHandler:]` method.
* @param URLRequest The request to create a data task with.
* @return A promise that is fulfilled when an HTTP response is received (with any response code),
* or is rejected with the error passed to the task completion.
*/
- (FBLPromise<GULURLSessionDataResponse *> *)gul_dataTaskPromiseWithRequest:
(NSURLRequest *)URLRequest;

@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#import "GoogleUtilities/Environment/URLSessionPromiseWrapper/NSURLSession+GULPromises.h"

#if __has_include(<FBLPromises/FBLPromises.h>)
#import <FBLPromises/FBLPromises.h>
#else
#import "FBLPromises.h"
#endif

#import "GoogleUtilities/Environment/URLSessionPromiseWrapper/GULURLSessionDataResponse.h"

@implementation NSURLSession (GULPromises)

- (FBLPromise<GULURLSessionDataResponse *> *)gul_dataTaskPromiseWithRequest:
(NSURLRequest *)URLRequest {
return [FBLPromise async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock reject) {
[[self dataTaskWithRequest:URLRequest
completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response,
NSError *_Nullable error) {
if (error) {
reject(error);
} else {
fulfill([[GULURLSessionDataResponse alloc]
initWithResponse:(NSHTTPURLResponse *)response
HTTPBody:data]);
}
}] resume];
}];
}

@end
100 changes: 100 additions & 0 deletions GoogleUtilities/Tests/Unit/Environment/NSURLSession+GULPromisesTests.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#import <XCTest/XCTest.h>

#import "FBLPromise+Testing.h"
#import "OCMock.h"
#import "SharedTestUtilities/URLSession/FIRURLSessionOCMockStub.h"

#import "GoogleUtilities/Environment/URLSessionPromiseWrapper/GULURLSessionDataResponse.h"
#import "GoogleUtilities/Environment/URLSessionPromiseWrapper/NSURLSession+GULPromises.h"

@interface NSURLSession_GULPromisesTests : XCTestCase
@property(nonatomic) NSURLSession *URLSession;
@property(nonatomic) id URLSessionMock;
@end

@implementation NSURLSession_GULPromisesTests

- (void)setUp {
self.URLSession = [NSURLSession
sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration]];
self.URLSessionMock = OCMPartialMock(self.URLSession);
}

- (void)tearDown {
[self.URLSessionMock stopMocking];
self.URLSessionMock = nil;
self.URLSession = nil;
}

- (void)testDataTaskPromiseWithRequestSuccess {
NSURL *url = [NSURL URLWithString:@"https://localhost"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];

NSHTTPURLResponse *expectedResponse = [[NSHTTPURLResponse alloc] initWithURL:url
statusCode:200
HTTPVersion:@"1.1"
headerFields:nil];
NSData *expectedBody = [@"body" dataUsingEncoding:NSUTF8StringEncoding];

[FIRURLSessionOCMockStub
stubURLSessionDataTaskWithResponse:expectedResponse
body:expectedBody
error:nil
URLSessionMock:self.URLSessionMock
requestValidationBlock:^BOOL(NSURLRequest *_Nonnull sentRequest) {
return [sentRequest isEqual:request];
}];

__auto_type taskPromise = [self.URLSessionMock gul_dataTaskPromiseWithRequest:request];

XCTAssert(FBLWaitForPromisesWithTimeout(0.5));

XCTAssertTrue(taskPromise.isFulfilled);
XCTAssertNil(taskPromise.error);
XCTAssertEqualObjects(taskPromise.value.HTTPResponse, expectedResponse);
XCTAssertEqualObjects(taskPromise.value.HTTPBody, expectedBody);
}

- (void)testDataTaskPromiseWithRequestError {
NSURL *url = [NSURL URLWithString:@"https://localhost"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];

NSError *expectedError = [NSError errorWithDomain:@"testDataTaskPromiseWithRequestError"
code:-1
userInfo:nil];

[FIRURLSessionOCMockStub
stubURLSessionDataTaskWithResponse:nil
body:nil
error:expectedError
URLSessionMock:self.URLSessionMock
requestValidationBlock:^BOOL(NSURLRequest *_Nonnull sentRequest) {
return [sentRequest isEqual:request];
}];

__auto_type taskPromise = [self.URLSessionMock gul_dataTaskPromiseWithRequest:request];

XCTAssert(FBLWaitForPromisesWithTimeout(0.5));

XCTAssertTrue(taskPromise.isRejected);
XCTAssertEqualObjects(taskPromise.error, expectedError);
XCTAssertNil(taskPromise.value);
}

@end
35 changes: 35 additions & 0 deletions SharedTestUtilities/URLSession/FIRURLSessionOCMockStub.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

typedef BOOL (^FIRRequestValidationBlock)(NSURLRequest *request);

@interface FIRURLSessionOCMockStub : NSObject

+ (id)stubURLSessionDataTaskWithResponse:(nullable NSHTTPURLResponse *)response
body:(nullable NSData *)body
error:(nullable NSError *)error
URLSessionMock:(id)URLSessionMock
requestValidationBlock:(nullable FIRRequestValidationBlock)requestValidationBlock;

+ (NSHTTPURLResponse *)HTTPResponseWithCode:(NSInteger)statusCode;

@end

NS_ASSUME_NONNULL_END
65 changes: 65 additions & 0 deletions SharedTestUtilities/URLSession/FIRURLSessionOCMockStub.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#import "SharedTestUtilities/URLSession/FIRURLSessionOCMockStub.h"

#import "OCMock.h"

@implementation FIRURLSessionOCMockStub

+ (id)stubURLSessionDataTaskWithResponse:(NSHTTPURLResponse *)response
body:(NSData *)body
error:(NSError *)error
URLSessionMock:(id)URLSessionMock
requestValidationBlock:(FIRRequestValidationBlock)requestValidationBlock {
id mockDataTask = OCMStrictClassMock([NSURLSessionDataTask class]);

// Validate request content.
FIRRequestValidationBlock nonOptionalRequestValidationBlock =
requestValidationBlock ?: ^BOOL(id request) {
return YES;
};

id URLRequestValidationArg = [OCMArg checkWithBlock:nonOptionalRequestValidationBlock];

// Save task completion to be called on the `[NSURLSessionDataTask resume]`
__block void (^taskCompletion)(NSData *, NSURLResponse *, NSError *);
id completionArg = [OCMArg checkWithBlock:^BOOL(id obj) {
taskCompletion = obj;
return YES;
}];

// Expect `dataTaskWithRequest` to be called.
OCMExpect([URLSessionMock dataTaskWithRequest:URLRequestValidationArg
completionHandler:completionArg])
.andReturn(mockDataTask);

// Expect the task to be resumed and call the task completion.
OCMExpect([(NSURLSessionDataTask *)mockDataTask resume]).andDo(^(NSInvocation *invocation) {
taskCompletion(body, response, error);
});

return mockDataTask;
}

+ (NSHTTPURLResponse *)HTTPResponseWithCode:(NSInteger)statusCode {
return [[NSHTTPURLResponse alloc] initWithURL:[NSURL URLWithString:@"http://localhost"]
statusCode:statusCode
HTTPVersion:@"HTTP/1.1"
headerFields:nil];
}

@end

0 comments on commit 7e70a6d

Please sign in to comment.