From 0b1b3efc8268437ca498d6ac5029da20c76ed24e Mon Sep 17 00:00:00 2001 From: Przemyslaw Stasiak Date: Thu, 20 Nov 2014 16:58:18 +0100 Subject: [PATCH 1/4] Added possibility to stub per single Nocilla instance - for LSHTTPStubURLProtocol. --- Nocilla.xcodeproj/project.pbxproj | 6 ++-- .../xcshareddata/xcschemes/Nocilla.xcscheme | 13 +++++++-- Nocilla/DSL/LSStubRequestDSL.h | 1 + .../NSURLRequest/LSHTTPStubURLProtocol.h | 3 ++ .../NSURLRequest/LSHTTPStubURLProtocol.m | 10 ++++++- .../Hooks/NSURLSession/LSNSURLSessionHook.h | 2 ++ .../Hooks/NSURLSession/LSNSURLSessionHook.m | 29 +++++++++++++++++-- Nocilla/LSNocilla.h | 7 +++++ Nocilla/LSNocilla.m | 15 +++++++++- 9 files changed, 77 insertions(+), 9 deletions(-) diff --git a/Nocilla.xcodeproj/project.pbxproj b/Nocilla.xcodeproj/project.pbxproj index 15c1ae2c..e4d03e4d 100644 --- a/Nocilla.xcodeproj/project.pbxproj +++ b/Nocilla.xcodeproj/project.pbxproj @@ -171,7 +171,7 @@ A085B83B15E30749007D33C1 /* Foundation.framework */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; A085B83F15E30749007D33C1 /* Nocilla-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Nocilla-Prefix.pch"; sourceTree = ""; }; A085B84015E30749007D33C1 /* Nocilla.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Nocilla.h; sourceTree = ""; }; - A085B84915E30749007D33C1 /* NocillaTests.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; name = NocillaTests.octest; path = NocillaTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + A085B84915E30749007D33C1 /* NocillaTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NocillaTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; A085B85415E30749007D33C1 /* NocillaTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "NocillaTests-Info.plist"; sourceTree = ""; }; A085B85615E30749007D33C1 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; A085B85915E30749007D33C1 /* AFNetworkingStubbingSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = AFNetworkingStubbingSpec.m; path = Hooks/NSURLRequest/AFNetworkingStubbingSpec.m; sourceTree = ""; }; @@ -405,7 +405,7 @@ isa = PBXGroup; children = ( A085B83815E30749007D33C1 /* libNocilla.a */, - A085B84915E30749007D33C1 /* NocillaTests.octest */, + A085B84915E30749007D33C1 /* NocillaTests.xctest */, ); name = Products; sourceTree = ""; @@ -600,7 +600,7 @@ ); name = NocillaTests; productName = NocillaTests; - productReference = A085B84915E30749007D33C1 /* NocillaTests.octest */; + productReference = A085B84915E30749007D33C1 /* NocillaTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ diff --git a/Nocilla.xcodeproj/xcshareddata/xcschemes/Nocilla.xcscheme b/Nocilla.xcodeproj/xcshareddata/xcschemes/Nocilla.xcscheme index 7279bb02..a041d40c 100644 --- a/Nocilla.xcodeproj/xcshareddata/xcschemes/Nocilla.xcscheme +++ b/Nocilla.xcodeproj/xcshareddata/xcschemes/Nocilla.xcscheme @@ -29,7 +29,7 @@ @@ -47,7 +47,7 @@ @@ -63,6 +63,15 @@ ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" allowLocationSimulation = "YES"> + + + + +#import "LSNocilla.h" @interface LSHTTPStubURLProtocol : NSURLProtocol ++ (void)registerNocilla:(LSNocilla *)nocilla; + @end diff --git a/Nocilla/Hooks/NSURLRequest/LSHTTPStubURLProtocol.m b/Nocilla/Hooks/NSURLRequest/LSHTTPStubURLProtocol.m index 4af21ce6..d51d064d 100644 --- a/Nocilla/Hooks/NSURLRequest/LSHTTPStubURLProtocol.m +++ b/Nocilla/Hooks/NSURLRequest/LSHTTPStubURLProtocol.m @@ -10,6 +10,8 @@ - (id)initWithURL:(NSURL*)URL statusCode:(NSInteger)statusCode headerFields:(NSD @implementation LSHTTPStubURLProtocol +static __weak LSNocilla *staticNocila; + + (BOOL)canInitWithRequest:(NSURLRequest *)request { return [@[ @"http", @"https" ] containsObject:request.URL.scheme]; } @@ -17,15 +19,21 @@ + (BOOL)canInitWithRequest:(NSURLRequest *)request { + (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request { return request; } + + (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b { return NO; } ++ (void)registerNocilla:(LSNocilla *)nocilla +{ + staticNocila = nocilla; +} + - (void)startLoading { NSURLRequest* request = [self request]; id client = [self client]; - LSStubResponse* stubbedResponse = [[LSNocilla sharedInstance] responseForRequest:request]; + LSStubResponse* stubbedResponse = [staticNocila responseForRequest:request]; if (stubbedResponse.shouldFail) { [client URLProtocol:self didFailWithError:stubbedResponse.error]; diff --git a/Nocilla/Hooks/NSURLSession/LSNSURLSessionHook.h b/Nocilla/Hooks/NSURLSession/LSNSURLSessionHook.h index aa6ec8c4..24fbac54 100644 --- a/Nocilla/Hooks/NSURLSession/LSNSURLSessionHook.h +++ b/Nocilla/Hooks/NSURLSession/LSNSURLSessionHook.h @@ -12,4 +12,6 @@ @interface LSNSURLSessionHook : LSHTTPClientHook +@property(weak, nonatomic) LSNocilla *nocilla; + @end diff --git a/Nocilla/Hooks/NSURLSession/LSNSURLSessionHook.m b/Nocilla/Hooks/NSURLSession/LSNSURLSessionHook.m index ab584f59..6bea3971 100644 --- a/Nocilla/Hooks/NSURLSession/LSNSURLSessionHook.m +++ b/Nocilla/Hooks/NSURLSession/LSNSURLSessionHook.m @@ -10,8 +10,32 @@ #import "LSHTTPStubURLProtocol.h" #import +@interface LSNSURLSessionHook () +@property(strong, nonatomic) Class urlProtocolClass; +@end + @implementation LSNSURLSessionHook +- (Class)urlProtocolClass +{ + if (!_urlProtocolClass) { + Class baseClass = [LSHTTPStubURLProtocol class]; + NSString * className = NSStringFromClass(baseClass); + + NSString * subclassName = [NSString stringWithFormat:@"%@%d", className, arc4random()]; + Class subclass = NSClassFromString(subclassName); + + if (subclass == nil) { + subclass = objc_allocateClassPair(baseClass, [subclassName UTF8String], 0); + if (subclass != nil) { + objc_registerClassPair(subclass); + _urlProtocolClass = subclass; + } + } + } + return _urlProtocolClass; +} + - (void)load { Class cls = NSClassFromString(@"__NSCFURLSessionConfiguration") ?: NSClassFromString(@"NSURLSessionConfiguration"); [self swizzleSelector:@selector(protocolClasses) fromClass:cls toClass:[self class]]; @@ -33,8 +57,9 @@ - (void)swizzleSelector:(SEL)selector fromClass:(Class)original toClass:(Class)s } - (NSArray *)protocolClasses { - return @[[LSHTTPStubURLProtocol class]]; + Class stubUrlProtocolClass = self.urlProtocolClass; + [stubUrlProtocolClass registerNocilla:self.nocilla]; + return @[stubUrlProtocolClass]; } - @end diff --git a/Nocilla/LSNocilla.h b/Nocilla/LSNocilla.h index aa85901e..9ee8fa02 100644 --- a/Nocilla/LSNocilla.h +++ b/Nocilla/LSNocilla.h @@ -2,17 +2,23 @@ #import "Nocilla.h" @class LSStubRequest; +@class LSStubRequestDSL; @class LSStubResponse; @class LSHTTPClientHook; @protocol LSHTTPRequest; +@protocol LSMatcheable; extern NSString * const LSUnexpectedRequest; +typedef LSStubRequestDSL *(^StubRequestMethod)(NSString *method, id url); + @interface LSNocilla : NSObject + (LSNocilla *)sharedInstance; @property (nonatomic, strong, readonly) NSArray *stubbedRequests; @property (nonatomic, assign, readonly, getter = isStarted) BOOL started; +@property (nonatomic, strong, readonly) StubRequestMethod stubRequest; + - (void)start; - (void)stop; @@ -22,4 +28,5 @@ extern NSString * const LSUnexpectedRequest; - (void)registerHook:(LSHTTPClientHook *)hook; - (LSStubResponse *)responseForRequest:(id)request; + @end diff --git a/Nocilla/LSNocilla.m b/Nocilla/LSNocilla.m index 78ce6f5c..5b685015 100644 --- a/Nocilla/LSNocilla.m +++ b/Nocilla/LSNocilla.m @@ -1,6 +1,8 @@ #import "LSNocilla.h" #import "LSNSURLHook.h" #import "LSStubRequest.h" +#import "LSStubRequestDSL.h" +#import "LSStubResponseDSL.h" #import "LSHTTPRequestDSLRepresentation.h" #import "LSASIHTTPRequestHook.h" #import "LSNSURLSessionHook.h" @@ -69,7 +71,7 @@ - (void)clearStubs { } - (LSStubResponse *)responseForRequest:(id)actualRequest { - NSArray* requests = [LSNocilla sharedInstance].stubbedRequests; + NSArray* requests = self.stubbedRequests; for(LSStubRequest *someStubbedRequest in requests) { if ([someStubbedRequest matchesRequest:actualRequest]) { @@ -95,6 +97,17 @@ - (BOOL)hookWasRegistered:(LSHTTPClientHook *)aHook { } return NO; } + +- ( LSStubRequestDSL *(^)(NSString *method, id url))stubRequest +{ + return ^(NSString *method, id url){ + LSStubRequest *request = [[LSStubRequest alloc] initWithMethod:method urlMatcher:url.matcher]; + LSStubRequestDSL *dsl = [[LSStubRequestDSL alloc] initWithRequest:request]; + [self addStubbedRequest:request]; + return dsl; + }; +} + #pragma mark - Private - (void)loadHooks { for (LSHTTPClientHook *hook in self.hooks) { From 40103e47a282774167e36055ffb46da4ce63fe26 Mon Sep 17 00:00:00 2001 From: Przemyslaw Stasiak Date: Thu, 20 Nov 2014 17:37:03 +0100 Subject: [PATCH 2/4] Registering Nocilla instance for LSNSURLSessionHook. --- Nocilla/LSNocilla.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Nocilla/LSNocilla.m b/Nocilla/LSNocilla.m index 5b685015..e69cab16 100644 --- a/Nocilla/LSNocilla.m +++ b/Nocilla/LSNocilla.m @@ -38,7 +38,9 @@ - (id)init { _hooks = [NSMutableArray array]; [self registerHook:[[LSNSURLHook alloc] init]]; if (NSClassFromString(@"NSURLSession") != nil) { - [self registerHook:[[LSNSURLSessionHook alloc] init]]; + LSNSURLSessionHook *sessionHook = [[LSNSURLSessionHook alloc] init]; + sessionHook.nocilla = self; + [self registerHook:sessionHook]; } [self registerHook:[[LSASIHTTPRequestHook alloc] init]]; } From 4f2823632036580b0ad29a2eae3dbdae1cd8669a Mon Sep 17 00:00:00 2001 From: Przemyslaw Stasiak Date: Thu, 20 Nov 2014 18:23:19 +0100 Subject: [PATCH 3/4] Now stubbing available also for NSUrlRequest, small refactor. --- .../NSURLRequest/LSHTTPStubURLProtocol.h | 1 + .../NSURLRequest/LSHTTPStubURLProtocol.m | 18 ++++++++++++++ Nocilla/Hooks/NSURLRequest/LSNSURLHook.h | 2 ++ Nocilla/Hooks/NSURLRequest/LSNSURLHook.m | 4 ++-- .../Hooks/NSURLSession/LSNSURLSessionHook.h | 2 +- .../Hooks/NSURLSession/LSNSURLSessionHook.m | 24 +------------------ Nocilla/LSNocilla.m | 12 ++++++++-- 7 files changed, 35 insertions(+), 28 deletions(-) diff --git a/Nocilla/Hooks/NSURLRequest/LSHTTPStubURLProtocol.h b/Nocilla/Hooks/NSURLRequest/LSHTTPStubURLProtocol.h index c27dff33..4d685f8e 100644 --- a/Nocilla/Hooks/NSURLRequest/LSHTTPStubURLProtocol.h +++ b/Nocilla/Hooks/NSURLRequest/LSHTTPStubURLProtocol.h @@ -4,5 +4,6 @@ @interface LSHTTPStubURLProtocol : NSURLProtocol + (void)registerNocilla:(LSNocilla *)nocilla; ++ (Class)randomSubclass; @end diff --git a/Nocilla/Hooks/NSURLRequest/LSHTTPStubURLProtocol.m b/Nocilla/Hooks/NSURLRequest/LSHTTPStubURLProtocol.m index d51d064d..f20caf83 100644 --- a/Nocilla/Hooks/NSURLRequest/LSHTTPStubURLProtocol.m +++ b/Nocilla/Hooks/NSURLRequest/LSHTTPStubURLProtocol.m @@ -3,6 +3,7 @@ #import "NSURLRequest+LSHTTPRequest.h" #import "LSStubRequest.h" #import "NSURLRequest+DSL.h" +#import @interface NSHTTPURLResponse(UndocumentedInitializer) - (id)initWithURL:(NSURL*)URL statusCode:(NSInteger)statusCode headerFields:(NSDictionary*)headerFields requestTime:(double)requestTime; @@ -12,6 +13,23 @@ @implementation LSHTTPStubURLProtocol static __weak LSNocilla *staticNocila; ++ (Class)randomSubclass +{ + Class baseClass = [self class]; + NSString * className = NSStringFromClass(baseClass); + + NSString * subclassName = [NSString stringWithFormat:@"%@%d", className, arc4random()]; + Class subclass = NSClassFromString(subclassName); + + if (subclass == nil) { + subclass = objc_allocateClassPair(baseClass, [subclassName UTF8String], 0); + if (subclass != nil) { + objc_registerClassPair(subclass); + } + } + return subclass; +} + + (BOOL)canInitWithRequest:(NSURLRequest *)request { return [@[ @"http", @"https" ] containsObject:request.URL.scheme]; } diff --git a/Nocilla/Hooks/NSURLRequest/LSNSURLHook.h b/Nocilla/Hooks/NSURLRequest/LSNSURLHook.h index abba2cbf..e17836b7 100644 --- a/Nocilla/Hooks/NSURLRequest/LSNSURLHook.h +++ b/Nocilla/Hooks/NSURLRequest/LSNSURLHook.h @@ -2,4 +2,6 @@ @interface LSNSURLHook : LSHTTPClientHook +@property(strong, nonatomic) Class urlProtocolClass; + @end diff --git a/Nocilla/Hooks/NSURLRequest/LSNSURLHook.m b/Nocilla/Hooks/NSURLRequest/LSNSURLHook.m index 77d70200..c7c6da99 100644 --- a/Nocilla/Hooks/NSURLRequest/LSNSURLHook.m +++ b/Nocilla/Hooks/NSURLRequest/LSNSURLHook.m @@ -4,11 +4,11 @@ @implementation LSNSURLHook - (void)load { - [NSURLProtocol registerClass:[LSHTTPStubURLProtocol class]]; + [NSURLProtocol registerClass:self.urlProtocolClass]; } - (void)unload { - [NSURLProtocol unregisterClass:[LSHTTPStubURLProtocol class]]; + [NSURLProtocol unregisterClass:self.urlProtocolClass]; } @end diff --git a/Nocilla/Hooks/NSURLSession/LSNSURLSessionHook.h b/Nocilla/Hooks/NSURLSession/LSNSURLSessionHook.h index 24fbac54..1f9e74e6 100644 --- a/Nocilla/Hooks/NSURLSession/LSNSURLSessionHook.h +++ b/Nocilla/Hooks/NSURLSession/LSNSURLSessionHook.h @@ -12,6 +12,6 @@ @interface LSNSURLSessionHook : LSHTTPClientHook -@property(weak, nonatomic) LSNocilla *nocilla; +@property(strong, nonatomic) Class urlProtocolClass; @end diff --git a/Nocilla/Hooks/NSURLSession/LSNSURLSessionHook.m b/Nocilla/Hooks/NSURLSession/LSNSURLSessionHook.m index 6bea3971..6b528342 100644 --- a/Nocilla/Hooks/NSURLSession/LSNSURLSessionHook.m +++ b/Nocilla/Hooks/NSURLSession/LSNSURLSessionHook.m @@ -11,31 +11,11 @@ #import @interface LSNSURLSessionHook () -@property(strong, nonatomic) Class urlProtocolClass; + @end @implementation LSNSURLSessionHook -- (Class)urlProtocolClass -{ - if (!_urlProtocolClass) { - Class baseClass = [LSHTTPStubURLProtocol class]; - NSString * className = NSStringFromClass(baseClass); - - NSString * subclassName = [NSString stringWithFormat:@"%@%d", className, arc4random()]; - Class subclass = NSClassFromString(subclassName); - - if (subclass == nil) { - subclass = objc_allocateClassPair(baseClass, [subclassName UTF8String], 0); - if (subclass != nil) { - objc_registerClassPair(subclass); - _urlProtocolClass = subclass; - } - } - } - return _urlProtocolClass; -} - - (void)load { Class cls = NSClassFromString(@"__NSCFURLSessionConfiguration") ?: NSClassFromString(@"NSURLSessionConfiguration"); [self swizzleSelector:@selector(protocolClasses) fromClass:cls toClass:[self class]]; @@ -47,7 +27,6 @@ - (void)unload { } - (void)swizzleSelector:(SEL)selector fromClass:(Class)original toClass:(Class)stub { - Method originalMethod = class_getInstanceMethod(original, selector); Method stubMethod = class_getInstanceMethod(stub, selector); if (!originalMethod || !stubMethod) { @@ -58,7 +37,6 @@ - (void)swizzleSelector:(SEL)selector fromClass:(Class)original toClass:(Class)s - (NSArray *)protocolClasses { Class stubUrlProtocolClass = self.urlProtocolClass; - [stubUrlProtocolClass registerNocilla:self.nocilla]; return @[stubUrlProtocolClass]; } diff --git a/Nocilla/LSNocilla.m b/Nocilla/LSNocilla.m index e69cab16..abb48cf7 100644 --- a/Nocilla/LSNocilla.m +++ b/Nocilla/LSNocilla.m @@ -7,6 +7,7 @@ #import "LSASIHTTPRequestHook.h" #import "LSNSURLSessionHook.h" #import "LSASIHTTPRequestHook.h" +#import "LSHTTPStubURLProtocol.h" NSString * const LSUnexpectedRequest = @"Unexpected Request"; @@ -36,10 +37,17 @@ - (id)init { if (self) { _mutableRequests = [NSMutableArray array]; _hooks = [NSMutableArray array]; - [self registerHook:[[LSNSURLHook alloc] init]]; + + Class stubUrlProtocolClass = [LSHTTPStubURLProtocol randomSubclass]; + [stubUrlProtocolClass registerNocilla:self]; + + LSNSURLHook *urlHook = [[LSNSURLHook alloc] init]; + urlHook.urlProtocolClass = stubUrlProtocolClass; + + [self registerHook:urlHook]; if (NSClassFromString(@"NSURLSession") != nil) { LSNSURLSessionHook *sessionHook = [[LSNSURLSessionHook alloc] init]; - sessionHook.nocilla = self; + sessionHook.urlProtocolClass = stubUrlProtocolClass; [self registerHook:sessionHook]; } [self registerHook:[[LSASIHTTPRequestHook alloc] init]]; From ce526f2784dfc3f0284c23baa1fd0c023c1315b7 Mon Sep 17 00:00:00 2001 From: Przemyslaw Stasiak Date: Thu, 20 Nov 2014 18:53:14 +0100 Subject: [PATCH 4/4] LSHTTPStubProtocol - now using nocilla instance per class. --- .../NSURLRequest/LSHTTPStubURLProtocol.m | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/Nocilla/Hooks/NSURLRequest/LSHTTPStubURLProtocol.m b/Nocilla/Hooks/NSURLRequest/LSHTTPStubURLProtocol.m index f20caf83..3bfb13ea 100644 --- a/Nocilla/Hooks/NSURLRequest/LSHTTPStubURLProtocol.m +++ b/Nocilla/Hooks/NSURLRequest/LSHTTPStubURLProtocol.m @@ -3,6 +3,7 @@ #import "NSURLRequest+LSHTTPRequest.h" #import "LSStubRequest.h" #import "NSURLRequest+DSL.h" +#import #import @interface NSHTTPURLResponse(UndocumentedInitializer) @@ -11,7 +12,15 @@ - (id)initWithURL:(NSURL*)URL statusCode:(NSInteger)statusCode headerFields:(NSD @implementation LSHTTPStubURLProtocol -static __weak LSNocilla *staticNocila; +static NSMapTable *nocillaWeakMapTable; + ++ (void)load +{ + if (!nocillaWeakMapTable) { + nocillaWeakMapTable = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsCopyIn + valueOptions:NSPointerFunctionsWeakMemory]; + } +} + (Class)randomSubclass { @@ -44,14 +53,20 @@ + (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b { + (void)registerNocilla:(LSNocilla *)nocilla { - staticNocila = nocilla; + NSString *className = NSStringFromClass([self class]); + [nocillaWeakMapTable setObject:nocilla forKey:className]; +} + ++ (LSNocilla *)nocilla +{ + return [nocillaWeakMapTable objectForKey:(NSStringFromClass([self class]))]; } - (void)startLoading { NSURLRequest* request = [self request]; id client = [self client]; - LSStubResponse* stubbedResponse = [staticNocila responseForRequest:request]; + LSStubResponse* stubbedResponse = [[[self class] nocilla] responseForRequest:request]; if (stubbedResponse.shouldFail) { [client URLProtocol:self didFailWithError:stubbedResponse.error];