├── .gitignore ├── .ruby-version ├── .travis.yml ├── .xctool-args ├── LICENSE ├── Nocilla.podspec ├── Nocilla.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ ├── Nocilla Mac.xcscheme │ ├── Nocilla tvOS.xcscheme │ └── Nocilla.xcscheme ├── Nocilla.xcworkspace └── contents.xcworkspacedata ├── Nocilla ├── Categories │ ├── NSData+Nocilla.h │ ├── NSData+Nocilla.m │ ├── NSString+Nocilla.h │ └── NSString+Nocilla.m ├── DSL │ ├── LSHTTPRequestDSLRepresentation.h │ ├── LSHTTPRequestDSLRepresentation.m │ ├── LSStubRequestDSL.h │ ├── LSStubRequestDSL.m │ ├── LSStubResponseDSL.h │ └── LSStubResponseDSL.m ├── Diff │ ├── LSHTTPRequestDiff.h │ └── LSHTTPRequestDiff.m ├── Hooks │ ├── ASIHTTPRequest │ │ ├── ASIHTTPRequestStub.h │ │ ├── ASIHTTPRequestStub.m │ │ ├── LSASIHTTPRequestAdapter.h │ │ ├── LSASIHTTPRequestAdapter.m │ │ ├── LSASIHTTPRequestHook.h │ │ └── LSASIHTTPRequestHook.m │ ├── LSHTTPClientHook.h │ ├── LSHTTPClientHook.m │ ├── NSURLRequest │ │ ├── LSHTTPStubURLProtocol.h │ │ ├── LSHTTPStubURLProtocol.m │ │ ├── LSNSURLHook.h │ │ ├── LSNSURLHook.m │ │ ├── NSURLRequest+DSL.h │ │ ├── NSURLRequest+DSL.m │ │ ├── NSURLRequest+LSHTTPRequest.h │ │ └── NSURLRequest+LSHTTPRequest.m │ └── NSURLSession │ │ ├── LSNSURLSessionHook.h │ │ └── LSNSURLSessionHook.m ├── Info.plist ├── LSNocilla.h ├── LSNocilla.m ├── Matchers │ ├── LSDataMatcher.h │ ├── LSDataMatcher.m │ ├── LSMatcheable.h │ ├── LSMatcher.h │ ├── LSMatcher.m │ ├── LSRegexMatcher.h │ ├── LSRegexMatcher.m │ ├── LSStringMatcher.h │ ├── LSStringMatcher.m │ ├── NSData+Matcheable.h │ ├── NSData+Matcheable.m │ ├── NSRegularExpression+Matcheable.h │ ├── NSRegularExpression+Matcheable.m │ ├── NSString+Matcheable.h │ └── NSString+Matcheable.m ├── Model │ ├── LSHTTPBody.h │ ├── LSHTTPRequest.h │ └── LSHTTPResponse.h ├── Nocilla-Prefix.pch ├── Nocilla.h └── Stubs │ ├── LSStubRequest.h │ ├── LSStubRequest.m │ ├── LSStubResponse.h │ └── LSStubResponse.m ├── NocillaTests ├── DSL │ └── LSHTTPRequestDSLRepresentationSpec.m ├── Diff │ └── LSHTTPRequestDiffSpec.m ├── Hooks │ ├── ASIHTTPRequest │ │ ├── ASIHTTPRequestStubbingSpec.m │ │ └── LSASIHTTPRequestHookSpec.m │ ├── NSURLRequest │ │ ├── AFNetworkingStubbingSpec.m │ │ ├── LSHTTPStubURLProtocolSpec.m │ │ ├── MKNetworkKitStubbingSpec.m │ │ ├── NSURLRequest+LSHTTPRequestSpec.m │ │ └── NSURLRequestHookSpec.m │ └── NSURLSession │ │ └── NSURLSessionStubbingSpec.m ├── Info.plist ├── LSNocillaSpec.m ├── LSStubResponseSpec.m ├── LSTestRequest.h ├── LSTestRequest.m ├── Matchers │ ├── LSDataMatcherSpec.m │ ├── LSRegexMatcherSpec.m │ └── LSStringMatcherSpec.m ├── NocillaTests-Info.plist ├── Stubs │ └── LSStubRequestSpec.m ├── Support │ ├── LSTestingConnection.h │ └── LSTestingConnection.m └── en.lproj │ └── InfoPlist.strings ├── Podfile ├── Podfile.lock └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | build/* 3 | *.pbxuser 4 | !default.pbxuser 5 | *.mode1v3 6 | !default.mode1v3 7 | *.mode2v3 8 | !default.mode2v3 9 | *.perspectivev3 10 | !default.perspectivev3 11 | *.xcworkspace 12 | !default.xcworkspace 13 | xcuserdata 14 | profile 15 | *.moved-aside 16 | .DS_Store 17 | Pods -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.2.2 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: objective-c 3 | 4 | before_install: 5 | - gem install xcpretty --no-rdoc --no-ri --no-document --quiet 6 | - gem install cocoapods --no-rdoc --no-ri --no-document --quiet 7 | 8 | install: 9 | - pod repo remove master 10 | - pod setup 11 | - pod install 12 | 13 | script: 14 | - set -o pipefail && xcodebuild -workspace Nocilla.xcworkspace -scheme 'Nocilla' -sdk iphonesimulator build test CODE_SIGN_IDENTITY=- | xcpretty -c 15 | -------------------------------------------------------------------------------- /.xctool-args: -------------------------------------------------------------------------------- 1 | [ 2 | "-workspace", "Nocilla.xcworkspace", 3 | "-scheme", "Nocilla", 4 | ] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Luis Solano Bonet 2 | MIT License 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Nocilla.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "Nocilla" 3 | s.version = "0.11.0" 4 | s.summary = "Stunning HTTP stubbing for iOS. Testing HTTP requests has never been easier." 5 | s.homepage = "https://github.com/luisobo/Nocilla" 6 | 7 | s.license = { :type => 'MIT', :file => 'LICENSE' } 8 | s.author = { "Luis Solano" => "contact@luissolano.com" } 9 | 10 | s.source = { :git => "https://github.com/luisobo/Nocilla.git", :tag => "0.11.0" } 11 | 12 | s.ios.deployment_target = '5.0' 13 | s.osx.deployment_target = '10.7' 14 | s.tvos.deployment_target = '9.0' 15 | 16 | s.source_files = 'Nocilla/**/*.{h,m}' 17 | 18 | s.public_header_files = [ 19 | 'Nocilla/Categories/NSData+Nocilla.h', 20 | 'Nocilla/Categories/NSString+Nocilla.h', 21 | 'Nocilla/DSL/LSStubRequestDSL.h', 22 | 'Nocilla/DSL/LSStubResponseDSL.h', 23 | 'Nocilla/LSNocilla.h', 24 | 'Nocilla/Matchers/LSMatcheable.h', 25 | 'Nocilla/Matchers/LSMatcher.h', 26 | 'Nocilla/Matchers/NSData+Matcheable.h', 27 | 'Nocilla/Matchers/NSRegularExpression+Matcheable.h', 28 | 'Nocilla/Matchers/NSString+Matcheable.h', 29 | 'Nocilla/Model/LSHTTPBody.h', 30 | 'Nocilla/Nocilla.h' 31 | ] 32 | 33 | s.requires_arc = true 34 | s.frameworks = 'CFNetwork' 35 | end 36 | -------------------------------------------------------------------------------- /Nocilla.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Nocilla.xcodeproj/xcshareddata/xcschemes/Nocilla Mac.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /Nocilla.xcodeproj/xcshareddata/xcschemes/Nocilla tvOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /Nocilla.xcodeproj/xcshareddata/xcschemes/Nocilla.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 69 | 70 | 76 | 77 | 78 | 79 | 83 | 84 | 85 | 86 | 92 | 93 | 95 | 96 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /Nocilla.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Nocilla/Categories/NSData+Nocilla.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "LSHTTPBody.h" 3 | 4 | @interface NSData (Nocilla) 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /Nocilla/Categories/NSData+Nocilla.m: -------------------------------------------------------------------------------- 1 | #import "NSData+Nocilla.h" 2 | 3 | @implementation NSData (Nocilla) 4 | 5 | - (NSData *)data { 6 | return self; 7 | } 8 | 9 | @end 10 | -------------------------------------------------------------------------------- /Nocilla/Categories/NSString+Nocilla.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "LSHTTPBody.h" 3 | 4 | @interface NSString (Nocilla) 5 | 6 | - (NSRegularExpression *)regex; 7 | 8 | @end 9 | -------------------------------------------------------------------------------- /Nocilla/Categories/NSString+Nocilla.m: -------------------------------------------------------------------------------- 1 | #import "NSString+Nocilla.h" 2 | 3 | @implementation NSString (Nocilla) 4 | 5 | - (NSRegularExpression *)regex { 6 | NSError *error = nil; 7 | NSRegularExpression *regex = [[NSRegularExpression alloc] initWithPattern:self options:0 error:&error]; 8 | if (error) { 9 | [NSException raise:NSInvalidArgumentException format:@"Invalid regex pattern: %@\nError: %@", self, error]; 10 | } 11 | return regex; 12 | } 13 | 14 | - (NSData *)data { 15 | return [self dataUsingEncoding:NSUTF8StringEncoding]; 16 | } 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Nocilla/DSL/LSHTTPRequestDSLRepresentation.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "LSHTTPRequest.h" 3 | 4 | @interface LSHTTPRequestDSLRepresentation : NSObject 5 | - (id)initWithRequest:(id)request; 6 | @end 7 | -------------------------------------------------------------------------------- /Nocilla/DSL/LSHTTPRequestDSLRepresentation.m: -------------------------------------------------------------------------------- 1 | #import "LSHTTPRequestDSLRepresentation.h" 2 | 3 | @interface LSHTTPRequestDSLRepresentation () 4 | @property (nonatomic, strong) id request; 5 | @end 6 | 7 | @implementation LSHTTPRequestDSLRepresentation 8 | - (id)initWithRequest:(id)request { 9 | self = [super init]; 10 | if (self) { 11 | _request = request; 12 | } 13 | return self; 14 | } 15 | 16 | - (NSString *)description { 17 | NSMutableString *result = [NSMutableString stringWithFormat:@"stubRequest(@\"%@\", @\"%@\")", self.request.method, [self.request.url absoluteString]]; 18 | if (self.request.headers.count) { 19 | [result appendString:@".\nwithHeaders(@{ "]; 20 | NSMutableArray *headerElements = [NSMutableArray arrayWithCapacity:self.request.headers.count]; 21 | 22 | NSArray *descriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"" ascending:YES]]; 23 | NSArray * sortedHeaders = [[self.request.headers allKeys] sortedArrayUsingDescriptors:descriptors]; 24 | 25 | for (NSString * header in sortedHeaders) { 26 | NSString *value = [self.request.headers objectForKey:header]; 27 | [headerElements addObject:[NSString stringWithFormat:@"@\"%@\": @\"%@\"", header, value]]; 28 | } 29 | [result appendString:[headerElements componentsJoinedByString:@", "]]; 30 | [result appendString:@" })"]; 31 | } 32 | if (self.request.body.length) { 33 | NSString *escapedBody = [[NSString alloc] initWithData:self.request.body encoding:NSUTF8StringEncoding]; 34 | escapedBody = [escapedBody stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""]; 35 | [result appendFormat:@".\nwithBody(@\"%@\")", escapedBody]; 36 | } 37 | return [NSString stringWithFormat:@"%@;", result]; 38 | } 39 | @end 40 | -------------------------------------------------------------------------------- /Nocilla/DSL/LSStubRequestDSL.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "NSString+Matcheable.h" 3 | #import "NSRegularExpression+Matcheable.h" 4 | #import "NSData+Matcheable.h" 5 | 6 | @class LSStubRequestDSL; 7 | @class LSStubResponseDSL; 8 | @class LSStubRequest; 9 | 10 | @protocol LSHTTPBody; 11 | 12 | typedef LSStubRequestDSL *(^WithHeaderMethod)(NSString *, NSString *); 13 | typedef LSStubRequestDSL *(^WithHeadersMethod)(NSDictionary *); 14 | typedef LSStubRequestDSL *(^AndBodyMethod)(id); 15 | typedef LSStubResponseDSL *(^AndReturnMethod)(NSInteger); 16 | typedef LSStubResponseDSL *(^AndReturnRawResponseMethod)(NSData *rawResponseData); 17 | typedef void (^AndFailWithErrorMethod)(NSError *error); 18 | 19 | @interface LSStubRequestDSL : NSObject 20 | - (id)initWithRequest:(LSStubRequest *)request; 21 | 22 | @property (nonatomic, strong, readonly) WithHeaderMethod withHeader; 23 | @property (nonatomic, strong, readonly) WithHeadersMethod withHeaders; 24 | @property (nonatomic, strong, readonly) AndBodyMethod withBody; 25 | @property (nonatomic, strong, readonly) AndReturnMethod andReturn; 26 | @property (nonatomic, strong, readonly) AndReturnRawResponseMethod andReturnRawResponse; 27 | @property (nonatomic, strong, readonly) AndFailWithErrorMethod andFailWithError; 28 | 29 | @end 30 | 31 | #ifdef __cplusplus 32 | extern "C" { 33 | #endif 34 | 35 | LSStubRequestDSL * stubRequest(NSString *method, id url); 36 | 37 | #ifdef __cplusplus 38 | } 39 | #endif 40 | -------------------------------------------------------------------------------- /Nocilla/DSL/LSStubRequestDSL.m: -------------------------------------------------------------------------------- 1 | #import "LSStubRequestDSL.h" 2 | #import "LSStubResponseDSL.h" 3 | #import "LSStubRequest.h" 4 | #import "LSNocilla.h" 5 | 6 | @interface LSStubRequestDSL () 7 | @property (nonatomic, strong) LSStubRequest *request; 8 | @end 9 | 10 | @implementation LSStubRequestDSL 11 | 12 | - (id)initWithRequest:(LSStubRequest *)request { 13 | self = [super init]; 14 | if (self) { 15 | _request = request; 16 | } 17 | return self; 18 | } 19 | - (WithHeadersMethod)withHeaders { 20 | return ^(NSDictionary *headers) { 21 | for (NSString *header in headers) { 22 | NSString *value = [headers objectForKey:header]; 23 | [self.request setHeader:header value:value]; 24 | } 25 | return self; 26 | }; 27 | } 28 | 29 | - (WithHeaderMethod)withHeader { 30 | return ^(NSString * header, NSString * value) { 31 | [self.request setHeader:header value:value]; 32 | return self; 33 | }; 34 | } 35 | 36 | - (AndBodyMethod)withBody { 37 | return ^(id body) { 38 | self.request.body = body.matcher; 39 | return self; 40 | }; 41 | } 42 | 43 | - (AndReturnMethod)andReturn { 44 | return ^(NSInteger statusCode) { 45 | self.request.response = [[LSStubResponse alloc] initWithStatusCode:statusCode]; 46 | LSStubResponseDSL *responseDSL = [[LSStubResponseDSL alloc] initWithResponse:self.request.response]; 47 | return responseDSL; 48 | }; 49 | } 50 | 51 | - (AndReturnRawResponseMethod)andReturnRawResponse { 52 | return ^(NSData *rawResponseData) { 53 | self.request.response = [[LSStubResponse alloc] initWithRawResponse:rawResponseData]; 54 | LSStubResponseDSL *responseDSL = [[LSStubResponseDSL alloc] initWithResponse:self.request.response]; 55 | return responseDSL; 56 | }; 57 | } 58 | 59 | - (AndFailWithErrorMethod)andFailWithError { 60 | return ^(NSError *error) { 61 | self.request.response = [[LSStubResponse alloc] initWithError:error]; 62 | }; 63 | } 64 | 65 | @end 66 | 67 | LSStubRequestDSL * stubRequest(NSString *method, id url) { 68 | LSStubRequest *request = [[LSStubRequest alloc] initWithMethod:method urlMatcher:url.matcher]; 69 | LSStubRequestDSL *dsl = [[LSStubRequestDSL alloc] initWithRequest:request]; 70 | [[LSNocilla sharedInstance] addStubbedRequest:request]; 71 | return dsl; 72 | } 73 | -------------------------------------------------------------------------------- /Nocilla/DSL/LSStubResponseDSL.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @class LSStubResponse; 4 | @class LSStubResponseDSL; 5 | 6 | @protocol LSHTTPBody; 7 | 8 | typedef LSStubResponseDSL *(^ResponseWithBodyMethod)(id); 9 | typedef LSStubResponseDSL *(^ResponseWithHeaderMethod)(NSString *, NSString *); 10 | typedef LSStubResponseDSL *(^ResponseWithHeadersMethod)(NSDictionary *); 11 | 12 | @interface LSStubResponseDSL : NSObject 13 | - (id)initWithResponse:(LSStubResponse *)response; 14 | 15 | @property (nonatomic, strong, readonly) ResponseWithHeaderMethod withHeader; 16 | @property (nonatomic, strong, readonly) ResponseWithHeadersMethod withHeaders; 17 | @property (nonatomic, strong, readonly) ResponseWithBodyMethod withBody; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Nocilla/DSL/LSStubResponseDSL.m: -------------------------------------------------------------------------------- 1 | #import "LSStubResponseDSL.h" 2 | #import "LSStubResponse.h" 3 | #import "LSHTTPBody.h" 4 | 5 | @interface LSStubResponseDSL () 6 | @property (nonatomic, strong) LSStubResponse *response; 7 | @end 8 | 9 | @implementation LSStubResponseDSL 10 | - (id)initWithResponse:(LSStubResponse *)response { 11 | self = [super init]; 12 | if (self) { 13 | _response = response; 14 | } 15 | return self; 16 | } 17 | - (ResponseWithHeaderMethod)withHeader { 18 | return ^(NSString * header, NSString * value) { 19 | [self.response setHeader:header value:value]; 20 | return self; 21 | }; 22 | } 23 | 24 | - (ResponseWithHeadersMethod)withHeaders; { 25 | return ^(NSDictionary *headers) { 26 | for (NSString *header in headers) { 27 | NSString *value = [headers objectForKey:header]; 28 | [self.response setHeader:header value:value]; 29 | } 30 | return self; 31 | }; 32 | } 33 | 34 | - (ResponseWithBodyMethod)withBody { 35 | return ^(id body) { 36 | self.response.body = [body data]; 37 | return self; 38 | }; 39 | } 40 | @end 41 | -------------------------------------------------------------------------------- /Nocilla/Diff/LSHTTPRequestDiff.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "LSHTTPRequest.h" 3 | 4 | @interface LSHTTPRequestDiff : NSObject 5 | @property (nonatomic, assign, readonly, getter = isEmpty) BOOL empty; 6 | 7 | - (id)initWithRequest:(id)oneRequest andRequest:(id)anotherRequest; 8 | @end 9 | -------------------------------------------------------------------------------- /Nocilla/Diff/LSHTTPRequestDiff.m: -------------------------------------------------------------------------------- 1 | #import "LSHTTPRequestDiff.h" 2 | 3 | @interface LSHTTPRequestDiff () 4 | @property (nonatomic, strong) idoneRequest; 5 | @property (nonatomic, strong) idanotherRequest; 6 | 7 | - (BOOL)isMethodDifferent; 8 | - (BOOL)isUrlDifferent; 9 | - (BOOL)areHeadersDifferent; 10 | - (BOOL)isBodyDifferent; 11 | 12 | - (void)appendMethodDiff:(NSMutableString *)diff; 13 | - (void)appendUrlDiff:(NSMutableString *)diff; 14 | - (void)appendHeadersDiff:(NSMutableString *)diff; 15 | - (void)appendBodyDiff:(NSMutableString *)diff; 16 | @end 17 | 18 | @implementation LSHTTPRequestDiff 19 | - (id)initWithRequest:(id)oneRequest andRequest:(id)anotherRequest { 20 | self = [super init]; 21 | if (self) { 22 | _oneRequest = oneRequest; 23 | _anotherRequest = anotherRequest; 24 | } 25 | return self; 26 | } 27 | 28 | - (BOOL)isEmpty { 29 | if ([self isMethodDifferent] || 30 | [self isUrlDifferent] || 31 | [self areHeadersDifferent] || 32 | [self isBodyDifferent]) { 33 | return NO; 34 | } 35 | return YES; 36 | } 37 | 38 | - (NSString *)description { 39 | NSMutableString *diff = [@"" mutableCopy]; 40 | if ([self isMethodDifferent]) { 41 | [self appendMethodDiff:diff]; 42 | } 43 | if ([self isUrlDifferent]) { 44 | [self appendUrlDiff:diff]; 45 | } 46 | if([self areHeadersDifferent]) { 47 | [self appendHeadersDiff:diff]; 48 | } 49 | if([self isBodyDifferent]) { 50 | [self appendBodyDiff:diff]; 51 | } 52 | return [NSString stringWithString:diff]; 53 | } 54 | 55 | #pragma mark - Private Methods 56 | - (BOOL)isMethodDifferent { 57 | return ![self.oneRequest.method isEqualToString:self.anotherRequest.method]; 58 | } 59 | 60 | - (BOOL)isUrlDifferent { 61 | return ![self.oneRequest.url isEqual:self.anotherRequest.url]; 62 | } 63 | 64 | - (BOOL)areHeadersDifferent { 65 | return ![self.oneRequest.headers isEqual:self.anotherRequest.headers]; 66 | } 67 | 68 | - (BOOL)isBodyDifferent { 69 | return (((self.oneRequest.body) && (![self.oneRequest.body isEqual:self.anotherRequest.body])) || 70 | ((self.anotherRequest.body) && (![self.anotherRequest.body isEqual:self.oneRequest.body]))); 71 | } 72 | 73 | - (void)appendMethodDiff:(NSMutableString *)diff { 74 | [diff appendFormat:@"- Method: %@\n+ Method: %@\n", self.oneRequest.method, self.anotherRequest.method]; 75 | } 76 | 77 | - (void)appendUrlDiff:(NSMutableString *)diff { 78 | [diff appendFormat:@"- URL: %@\n+ URL: %@\n", [self.oneRequest.url absoluteString], [self.anotherRequest.url absoluteString]]; 79 | } 80 | 81 | - (void)appendHeadersDiff:(NSMutableString *)diff { 82 | [diff appendString:@" Headers:\n"]; 83 | NSSet *headersInOneButNotInTheOther = [self.oneRequest.headers keysOfEntriesPassingTest:^BOOL(id key, id obj, BOOL *stop) { 84 | return ![self.anotherRequest.headers objectForKey:key] || ![obj isEqual:[self.anotherRequest.headers objectForKey:key]]; 85 | }]; 86 | NSSet *headersInTheOtherButNotInOne = [self.anotherRequest.headers keysOfEntriesPassingTest:^BOOL(id key, id obj, BOOL *stop) { 87 | return ![self.oneRequest.headers objectForKey:key] || ![obj isEqual:[self.oneRequest.headers objectForKey:key]]; 88 | }]; 89 | 90 | NSArray *descriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"" ascending:YES]]; 91 | NSArray * sortedHeadersInOneButNotInTheOther = [headersInOneButNotInTheOther sortedArrayUsingDescriptors:descriptors]; 92 | NSArray * sortedHeadersInTheOtherButNotInOne = [headersInTheOtherButNotInOne sortedArrayUsingDescriptors:descriptors]; 93 | for (NSString *header in sortedHeadersInOneButNotInTheOther) { 94 | NSString *value = [self.oneRequest.headers objectForKey:header]; 95 | [diff appendFormat:@"-\t\"%@\": \"%@\"\n", header, value]; 96 | 97 | } 98 | for (NSString *header in sortedHeadersInTheOtherButNotInOne) { 99 | NSString *value = [self.anotherRequest.headers objectForKey:header]; 100 | [diff appendFormat:@"+\t\"%@\": \"%@\"\n", header, value]; 101 | } 102 | } 103 | 104 | - (void)appendBodyDiff:(NSMutableString *)diff { 105 | NSString *oneBody = [[NSString alloc] initWithData:self.oneRequest.body encoding:NSUTF8StringEncoding]; 106 | if (oneBody.length) { 107 | [diff appendFormat:@"- Body: \"%@\"\n", oneBody]; 108 | } 109 | NSString *anotherBody = [[NSString alloc] initWithData:self.anotherRequest.body encoding:NSUTF8StringEncoding]; 110 | if (anotherBody.length) { 111 | [diff appendFormat:@"+ Body: \"%@\"\n", anotherBody]; 112 | } 113 | } 114 | @end 115 | -------------------------------------------------------------------------------- /Nocilla/Hooks/ASIHTTPRequest/ASIHTTPRequestStub.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface ASIHTTPRequestStub : NSObject 4 | - (int)stub_responseStatusCode; 5 | - (NSData *)stub_responseData; 6 | - (NSDictionary *)stub_responseHeaders; 7 | - (void)stub_startRequest; 8 | @end 9 | -------------------------------------------------------------------------------- /Nocilla/Hooks/ASIHTTPRequest/ASIHTTPRequestStub.m: -------------------------------------------------------------------------------- 1 | #import "ASIHTTPRequestStub.h" 2 | #import "LSStubResponse.h" 3 | #import "LSNocilla.h" 4 | #import "LSASIHTTPRequestAdapter.h" 5 | #import 6 | 7 | @interface ASIHTTPRequestStub () 8 | @property (nonatomic, strong) LSStubResponse *stubResponse; 9 | @end 10 | 11 | @interface ASIHTTPRequestStub (Private) 12 | - (void)failWithError:(NSError *)error; 13 | - (void)requestFinished; 14 | - (void)markAsFinished; 15 | @end 16 | 17 | static void const * ASIHTTPRequestStubResponseKey = &ASIHTTPRequestStubResponseKey; 18 | 19 | @implementation ASIHTTPRequestStub 20 | 21 | - (void)setStubResponse:(LSStubResponse *)stubResponse { 22 | objc_setAssociatedObject(self, ASIHTTPRequestStubResponseKey, stubResponse, OBJC_ASSOCIATION_RETAIN); 23 | } 24 | 25 | - (LSStubResponse *)stubResponse { 26 | return objc_getAssociatedObject(self, ASIHTTPRequestStubResponseKey); 27 | } 28 | 29 | - (int)stub_responseStatusCode { 30 | return (int)self.stubResponse.statusCode; 31 | } 32 | 33 | - (NSData *)stub_responseData { 34 | return self.stubResponse.body; 35 | } 36 | 37 | - (NSDictionary *)stub_responseHeaders { 38 | return self.stubResponse.headers; 39 | } 40 | 41 | - (void)stub_startRequest { 42 | self.stubResponse = [[LSNocilla sharedInstance] responseForRequest:[[LSASIHTTPRequestAdapter alloc] initWithASIHTTPRequest:(id)self]]; 43 | 44 | if (self.stubResponse.shouldFail) { 45 | [self failWithError:self.stubResponse.error]; 46 | } else { 47 | [self requestFinished]; 48 | } 49 | [self markAsFinished]; 50 | } 51 | 52 | @end -------------------------------------------------------------------------------- /Nocilla/Hooks/ASIHTTPRequest/LSASIHTTPRequestAdapter.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "LSHTTPRequest.h" 3 | 4 | @class ASIHTTPRequest; 5 | 6 | @interface LSASIHTTPRequestAdapter : NSObject 7 | 8 | - (instancetype)initWithASIHTTPRequest:(ASIHTTPRequest *)request; 9 | 10 | @end 11 | -------------------------------------------------------------------------------- /Nocilla/Hooks/ASIHTTPRequest/LSASIHTTPRequestAdapter.m: -------------------------------------------------------------------------------- 1 | #import "LSASIHTTPRequestAdapter.h" 2 | 3 | @interface ASIHTTPRequest 4 | 5 | @property (nonatomic, strong, readonly) NSURL *url; 6 | @property (nonatomic, strong, readonly) NSString *requestMethod; 7 | @property (nonatomic, strong, readonly) NSDictionary *requestHeaders; 8 | @property (nonatomic, strong, readonly) NSData *postBody; 9 | 10 | @end 11 | 12 | @interface LSASIHTTPRequestAdapter () 13 | @property (nonatomic, strong) ASIHTTPRequest *request; 14 | @end 15 | 16 | @implementation LSASIHTTPRequestAdapter 17 | 18 | - (instancetype)initWithASIHTTPRequest:(ASIHTTPRequest *)request { 19 | self = [super init]; 20 | if (self) { 21 | _request = request; 22 | } 23 | return self; 24 | } 25 | 26 | - (NSURL *)url { 27 | return self.request.url; 28 | } 29 | 30 | - (NSString *)method { 31 | return self.request.requestMethod; 32 | } 33 | 34 | - (NSDictionary *)headers { 35 | return self.request.requestHeaders; 36 | } 37 | 38 | - (NSData *)body { 39 | return self.request.postBody; 40 | } 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /Nocilla/Hooks/ASIHTTPRequest/LSASIHTTPRequestHook.h: -------------------------------------------------------------------------------- 1 | #import "LSHTTPClientHook.h" 2 | 3 | @interface LSASIHTTPRequestHook : LSHTTPClientHook 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /Nocilla/Hooks/ASIHTTPRequest/LSASIHTTPRequestHook.m: -------------------------------------------------------------------------------- 1 | #import "LSASIHTTPRequestHook.h" 2 | #import "ASIHTTPRequestStub.h" 3 | #import 4 | 5 | @implementation LSASIHTTPRequestHook 6 | 7 | - (void)load { 8 | if (!NSClassFromString(@"ASIHTTPRequest")) return; 9 | [self swizzleASIHTTPRequest]; 10 | } 11 | 12 | - (void)unload { 13 | if (!NSClassFromString(@"ASIHTTPRequest")) return; 14 | [self swizzleASIHTTPRequest]; 15 | } 16 | 17 | #pragma mark - Internal Methods 18 | 19 | - (void)swizzleASIHTTPRequest { 20 | [self swizzleASIHTTPSelector:NSSelectorFromString(@"responseStatusCode") withSelector:@selector(stub_responseStatusCode)]; 21 | [self swizzleASIHTTPSelector:NSSelectorFromString(@"responseData") withSelector:@selector(stub_responseData)]; 22 | [self swizzleASIHTTPSelector:NSSelectorFromString(@"responseHeaders") withSelector:@selector(stub_responseHeaders)]; 23 | [self swizzleASIHTTPSelector:NSSelectorFromString(@"startRequest") withSelector:@selector(stub_startRequest)]; 24 | [self addMethodToASIHTTPRequest:NSSelectorFromString(@"stubResponse")]; 25 | [self addMethodToASIHTTPRequest:NSSelectorFromString(@"setStubResponse:")]; 26 | } 27 | 28 | - (void)swizzleASIHTTPSelector:(SEL)original withSelector:(SEL)stub { 29 | Class asiHttpRequest = NSClassFromString(@"ASIHTTPRequest"); 30 | Method originalMethod = class_getInstanceMethod(asiHttpRequest, original); 31 | Method stubMethod = class_getInstanceMethod([ASIHTTPRequestStub class], stub); 32 | if (!originalMethod || !stubMethod) { 33 | [self fail]; 34 | } 35 | method_exchangeImplementations(originalMethod, stubMethod); 36 | } 37 | 38 | - (void)addMethodToASIHTTPRequest:(SEL)newMethod { 39 | Method method = class_getInstanceMethod([ASIHTTPRequestStub class], newMethod); 40 | const char *types = method_getTypeEncoding(method); 41 | class_addMethod(NSClassFromString(@"ASIHTTPRequest"), newMethod, class_getMethodImplementation([ASIHTTPRequestStub class], newMethod), types); 42 | } 43 | 44 | - (void)fail { 45 | [NSException raise:NSInternalInconsistencyException format:@"Couldn't load ASIHTTPRequest hook."]; 46 | } 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /Nocilla/Hooks/LSHTTPClientHook.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface LSHTTPClientHook : NSObject 4 | - (void)load; 5 | - (void)unload; 6 | @end 7 | -------------------------------------------------------------------------------- /Nocilla/Hooks/LSHTTPClientHook.m: -------------------------------------------------------------------------------- 1 | #import "LSHTTPClientHook.h" 2 | 3 | @implementation LSHTTPClientHook 4 | - (void)load { 5 | [NSException raise:NSInternalInconsistencyException 6 | format:@"Method '%@' not implemented. Subclass '%@' and override it", NSStringFromSelector(_cmd), NSStringFromClass([self class])]; 7 | } 8 | 9 | - (void)unload { 10 | [NSException raise:NSInternalInconsistencyException 11 | format:@"Method '%@' not implemented. Subclass '%@' and override it", NSStringFromSelector(_cmd), NSStringFromClass([self class])]; 12 | } 13 | @end 14 | -------------------------------------------------------------------------------- /Nocilla/Hooks/NSURLRequest/LSHTTPStubURLProtocol.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface LSHTTPStubURLProtocol : NSURLProtocol 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /Nocilla/Hooks/NSURLRequest/LSHTTPStubURLProtocol.m: -------------------------------------------------------------------------------- 1 | #import "LSHTTPStubURLProtocol.h" 2 | #import "LSNocilla.h" 3 | #import "NSURLRequest+LSHTTPRequest.h" 4 | #import "LSStubRequest.h" 5 | #import "NSURLRequest+DSL.h" 6 | 7 | @implementation LSHTTPStubURLProtocol 8 | 9 | + (BOOL)canInitWithRequest:(NSURLRequest *)request { 10 | return [@[ @"http", @"https" ] containsObject:request.URL.scheme]; 11 | } 12 | 13 | + (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request { 14 | return request; 15 | } 16 | + (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b { 17 | return NO; 18 | } 19 | 20 | - (void)startLoading { 21 | NSURLRequest* request = [self request]; 22 | id client = [self client]; 23 | 24 | LSStubResponse* stubbedResponse = [[LSNocilla sharedInstance] responseForRequest:request]; 25 | 26 | NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage]; 27 | [cookieStorage setCookies:[NSHTTPCookie cookiesWithResponseHeaderFields:stubbedResponse.headers forURL:request.url] 28 | forURL:request.URL mainDocumentURL:request.URL]; 29 | 30 | if (stubbedResponse.shouldFail) { 31 | [client URLProtocol:self didFailWithError:stubbedResponse.error]; 32 | } else { 33 | NSHTTPURLResponse* urlResponse = [[NSHTTPURLResponse alloc] initWithURL:request.URL 34 | statusCode:stubbedResponse.statusCode 35 | HTTPVersion:nil 36 | headerFields:stubbedResponse.headers]; 37 | 38 | if (stubbedResponse.statusCode < 300 || stubbedResponse.statusCode > 399 39 | || stubbedResponse.statusCode == 304 || stubbedResponse.statusCode == 305 ) { 40 | NSData *body = stubbedResponse.body; 41 | 42 | [client URLProtocol:self didReceiveResponse:urlResponse 43 | cacheStoragePolicy:NSURLCacheStorageNotAllowed]; 44 | [client URLProtocol:self didLoadData:body]; 45 | [client URLProtocolDidFinishLoading:self]; 46 | } else { 47 | 48 | NSURL *newURL = [NSURL URLWithString:[stubbedResponse.headers objectForKey:@"Location"] relativeToURL:request.URL]; 49 | NSMutableURLRequest *redirectRequest = [NSMutableURLRequest requestWithURL:newURL]; 50 | 51 | [redirectRequest setAllHTTPHeaderFields:[NSHTTPCookie requestHeaderFieldsWithCookies:[cookieStorage cookiesForURL:newURL]]]; 52 | 53 | [client URLProtocol:self 54 | wasRedirectedToRequest:redirectRequest 55 | redirectResponse:urlResponse]; 56 | // According to: https://developer.apple.com/library/ios/samplecode/CustomHTTPProtocol/Listings/CustomHTTPProtocol_Core_Code_CustomHTTPProtocol_m.html 57 | // needs to abort the original request 58 | [client URLProtocol:self didFailWithError:[NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil]]; 59 | 60 | } 61 | } 62 | } 63 | 64 | - (void)stopLoading { 65 | } 66 | 67 | @end 68 | -------------------------------------------------------------------------------- /Nocilla/Hooks/NSURLRequest/LSNSURLHook.h: -------------------------------------------------------------------------------- 1 | #import "LSHTTPClientHook.h" 2 | 3 | @interface LSNSURLHook : LSHTTPClientHook 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /Nocilla/Hooks/NSURLRequest/LSNSURLHook.m: -------------------------------------------------------------------------------- 1 | #import "LSNSURLHook.h" 2 | #import "LSHTTPStubURLProtocol.h" 3 | 4 | @implementation LSNSURLHook 5 | 6 | - (void)load { 7 | [NSURLProtocol registerClass:[LSHTTPStubURLProtocol class]]; 8 | } 9 | 10 | - (void)unload { 11 | [NSURLProtocol unregisterClass:[LSHTTPStubURLProtocol class]]; 12 | } 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /Nocilla/Hooks/NSURLRequest/NSURLRequest+DSL.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface NSURLRequest (DSL) 4 | - (NSString *)toNocillaDSL; 5 | @end 6 | -------------------------------------------------------------------------------- /Nocilla/Hooks/NSURLRequest/NSURLRequest+DSL.m: -------------------------------------------------------------------------------- 1 | #import "NSURLRequest+DSL.h" 2 | #import "LSHTTPRequestDSLRepresentation.h" 3 | #import "NSURLRequest+LSHTTPRequest.h" 4 | 5 | @implementation NSURLRequest (DSL) 6 | - (NSString *)toNocillaDSL { 7 | return [[[LSHTTPRequestDSLRepresentation alloc] initWithRequest:self] description]; 8 | } 9 | @end 10 | -------------------------------------------------------------------------------- /Nocilla/Hooks/NSURLRequest/NSURLRequest+LSHTTPRequest.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "LSHTTPRequest.h" 3 | 4 | @interface NSURLRequest (LSHTTPRequest) 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /Nocilla/Hooks/NSURLRequest/NSURLRequest+LSHTTPRequest.m: -------------------------------------------------------------------------------- 1 | #import "NSURLRequest+LSHTTPRequest.h" 2 | 3 | @implementation NSURLRequest (LSHTTPRequest) 4 | 5 | - (NSURL*)url { 6 | return self.URL; 7 | } 8 | 9 | - (NSString *)method { 10 | return self.HTTPMethod; 11 | } 12 | 13 | - (NSDictionary *)headers { 14 | return self.allHTTPHeaderFields; 15 | } 16 | 17 | - (NSData *)body { 18 | if (self.HTTPBodyStream) { 19 | NSInputStream *stream = self.HTTPBodyStream; 20 | NSMutableData *data = [NSMutableData data]; 21 | [stream open]; 22 | size_t bufferSize = 4096; 23 | uint8_t *buffer = malloc(bufferSize); 24 | if (buffer == NULL) { 25 | [NSException raise:@"NocillaMallocFailure" format:@"Could not allocate %zu bytes to read HTTPBodyStream", bufferSize]; 26 | } 27 | while ([stream hasBytesAvailable]) { 28 | NSInteger bytesRead = [stream read:buffer maxLength:bufferSize]; 29 | if (bytesRead > 0) { 30 | NSData *readData = [NSData dataWithBytes:buffer length:bytesRead]; 31 | [data appendData:readData]; 32 | } else if (bytesRead < 0) { 33 | [NSException raise:@"NocillaStreamReadError" format:@"An error occurred while reading HTTPBodyStream (%ld)", (long)bytesRead]; 34 | } else if (bytesRead == 0) { 35 | break; 36 | } 37 | } 38 | free(buffer); 39 | [stream close]; 40 | 41 | return data; 42 | } 43 | 44 | return self.HTTPBody; 45 | } 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /Nocilla/Hooks/NSURLSession/LSNSURLSessionHook.h: -------------------------------------------------------------------------------- 1 | // 2 | // LSNSURLSessionHook.h 3 | // Nocilla 4 | // 5 | // Created by Luis Solano Bonet on 08/01/14. 6 | // Copyright (c) 2014 Luis Solano Bonet. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "LSHTTPClientHook.h" 12 | 13 | @interface LSNSURLSessionHook : LSHTTPClientHook 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Nocilla/Hooks/NSURLSession/LSNSURLSessionHook.m: -------------------------------------------------------------------------------- 1 | // 2 | // LSNSURLSessionHook.m 3 | // Nocilla 4 | // 5 | // Created by Luis Solano Bonet on 08/01/14. 6 | // Copyright (c) 2014 Luis Solano Bonet. All rights reserved. 7 | // 8 | 9 | #import "LSNSURLSessionHook.h" 10 | #import "LSHTTPStubURLProtocol.h" 11 | #import 12 | 13 | @implementation LSNSURLSessionHook 14 | 15 | - (void)load { 16 | Class cls = NSClassFromString(@"__NSCFURLSessionConfiguration") ?: NSClassFromString(@"NSURLSessionConfiguration"); 17 | [self swizzleSelector:@selector(protocolClasses) fromClass:cls toClass:[self class]]; 18 | } 19 | 20 | - (void)unload { 21 | Class cls = NSClassFromString(@"__NSCFURLSessionConfiguration") ?: NSClassFromString(@"NSURLSessionConfiguration"); 22 | [self swizzleSelector:@selector(protocolClasses) fromClass:cls toClass:[self class]]; 23 | } 24 | 25 | - (void)swizzleSelector:(SEL)selector fromClass:(Class)original toClass:(Class)stub { 26 | 27 | Method originalMethod = class_getInstanceMethod(original, selector); 28 | Method stubMethod = class_getInstanceMethod(stub, selector); 29 | if (!originalMethod || !stubMethod) { 30 | [NSException raise:NSInternalInconsistencyException format:@"Couldn't load NSURLSession hook."]; 31 | } 32 | method_exchangeImplementations(originalMethod, stubMethod); 33 | } 34 | 35 | - (NSArray *)protocolClasses { 36 | return @[[LSHTTPStubURLProtocol class]]; 37 | } 38 | 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /Nocilla/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Nocilla/LSNocilla.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "Nocilla.h" 3 | 4 | @class LSStubRequest; 5 | @class LSStubResponse; 6 | @class LSHTTPClientHook; 7 | @protocol LSHTTPRequest; 8 | 9 | extern NSString * const LSUnexpectedRequest; 10 | 11 | @interface LSNocilla : NSObject 12 | + (LSNocilla *)sharedInstance; 13 | 14 | @property (nonatomic, strong, readonly) NSArray *stubbedRequests; 15 | @property (nonatomic, assign, readonly, getter = isStarted) BOOL started; 16 | 17 | - (void)start; 18 | - (void)stop; 19 | - (void)addStubbedRequest:(LSStubRequest *)request; 20 | - (void)clearStubs; 21 | 22 | - (void)registerHook:(LSHTTPClientHook *)hook; 23 | 24 | - (LSStubResponse *)responseForRequest:(id)request; 25 | @end 26 | -------------------------------------------------------------------------------- /Nocilla/LSNocilla.m: -------------------------------------------------------------------------------- 1 | #import "LSNocilla.h" 2 | #import "LSNSURLHook.h" 3 | #import "LSStubRequest.h" 4 | #import "LSHTTPRequestDSLRepresentation.h" 5 | #import "LSASIHTTPRequestHook.h" 6 | #import "LSNSURLSessionHook.h" 7 | #import "LSASIHTTPRequestHook.h" 8 | 9 | NSString * const LSUnexpectedRequest = @"Unexpected Request"; 10 | 11 | @interface LSNocilla () 12 | @property (nonatomic, strong) NSMutableArray *mutableRequests; 13 | @property (nonatomic, strong) NSMutableArray *hooks; 14 | @property (nonatomic, assign, getter = isStarted) BOOL started; 15 | 16 | - (void)loadHooks; 17 | - (void)unloadHooks; 18 | @end 19 | 20 | static LSNocilla *sharedInstace = nil; 21 | 22 | @implementation LSNocilla 23 | 24 | + (LSNocilla *)sharedInstance { 25 | static dispatch_once_t onceToken; 26 | dispatch_once(&onceToken, ^{ 27 | sharedInstace = [[self alloc] init]; 28 | }); 29 | return sharedInstace; 30 | } 31 | 32 | - (id)init { 33 | self = [super init]; 34 | if (self) { 35 | _mutableRequests = [NSMutableArray array]; 36 | _hooks = [NSMutableArray array]; 37 | [self registerHook:[[LSNSURLHook alloc] init]]; 38 | if (NSClassFromString(@"NSURLSession") != nil) { 39 | [self registerHook:[[LSNSURLSessionHook alloc] init]]; 40 | } 41 | [self registerHook:[[LSASIHTTPRequestHook alloc] init]]; 42 | } 43 | return self; 44 | } 45 | 46 | - (NSArray *)stubbedRequests { 47 | return [NSArray arrayWithArray:self.mutableRequests]; 48 | } 49 | 50 | - (void)start { 51 | if (!self.isStarted){ 52 | [self loadHooks]; 53 | self.started = YES; 54 | } 55 | } 56 | 57 | - (void)stop { 58 | [self unloadHooks]; 59 | [self clearStubs]; 60 | self.started = NO; 61 | } 62 | 63 | - (void)addStubbedRequest:(LSStubRequest *)request { 64 | NSUInteger index = [self.mutableRequests indexOfObject:request]; 65 | 66 | if (index == NSNotFound) { 67 | [self.mutableRequests addObject:request]; 68 | return; 69 | } 70 | 71 | [self.mutableRequests replaceObjectAtIndex:index withObject:request]; 72 | } 73 | 74 | - (void)clearStubs { 75 | [self.mutableRequests removeAllObjects]; 76 | } 77 | 78 | - (LSStubResponse *)responseForRequest:(id)actualRequest { 79 | NSArray* requests = [LSNocilla sharedInstance].stubbedRequests; 80 | 81 | for(LSStubRequest *someStubbedRequest in requests) { 82 | if ([someStubbedRequest matchesRequest:actualRequest]) { 83 | return someStubbedRequest.response; 84 | } 85 | } 86 | [NSException raise:@"NocillaUnexpectedRequest" format:@"An unexpected HTTP request was fired.\n\nUse this snippet to stub the request:\n%@\n", [[[LSHTTPRequestDSLRepresentation alloc] initWithRequest:actualRequest] description]]; 87 | 88 | return nil; 89 | } 90 | 91 | - (void)registerHook:(LSHTTPClientHook *)hook { 92 | if (![self hookWasRegistered:hook]) { 93 | [[self hooks] addObject:hook]; 94 | } 95 | } 96 | 97 | - (BOOL)hookWasRegistered:(LSHTTPClientHook *)aHook { 98 | for (LSHTTPClientHook *hook in self.hooks) { 99 | if ([hook isMemberOfClass: [aHook class]]) { 100 | return YES; 101 | } 102 | } 103 | return NO; 104 | } 105 | #pragma mark - Private 106 | - (void)loadHooks { 107 | for (LSHTTPClientHook *hook in self.hooks) { 108 | [hook load]; 109 | } 110 | } 111 | 112 | - (void)unloadHooks { 113 | for (LSHTTPClientHook *hook in self.hooks) { 114 | [hook unload]; 115 | } 116 | } 117 | 118 | @end 119 | -------------------------------------------------------------------------------- /Nocilla/Matchers/LSDataMatcher.h: -------------------------------------------------------------------------------- 1 | // 2 | // LSDataMatcher.h 3 | // Nocilla 4 | // 5 | // Created by Luis Solano Bonet on 09/11/14. 6 | // Copyright (c) 2014 Luis Solano Bonet. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "LSMatcher.h" 11 | 12 | @interface LSDataMatcher : LSMatcher 13 | 14 | - (instancetype)initWithData:(NSData *)data; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Nocilla/Matchers/LSDataMatcher.m: -------------------------------------------------------------------------------- 1 | // 2 | // LSDataMatcher.m 3 | // Nocilla 4 | // 5 | // Created by Luis Solano Bonet on 09/11/14. 6 | // Copyright (c) 2014 Luis Solano Bonet. All rights reserved. 7 | // 8 | 9 | #import "LSDataMatcher.h" 10 | 11 | @interface LSDataMatcher () 12 | 13 | @property (nonatomic, copy) NSData *data; 14 | 15 | @end 16 | 17 | @implementation LSDataMatcher 18 | 19 | - (instancetype)initWithData:(NSData *)data { 20 | self = [super init]; 21 | 22 | if (self) { 23 | _data = data; 24 | } 25 | return self; 26 | } 27 | 28 | - (BOOL)matchesData:(NSData *)data { 29 | return [self.data isEqualToData:data]; 30 | } 31 | 32 | 33 | #pragma mark - Equality 34 | 35 | - (BOOL)isEqual:(id)object { 36 | if (self == object) { 37 | return YES; 38 | } 39 | 40 | if (![object isKindOfClass:[LSDataMatcher class]]) { 41 | return NO; 42 | } 43 | 44 | return [self.data isEqual:((LSDataMatcher *)object).data]; 45 | } 46 | 47 | - (NSUInteger)hash { 48 | return self.data.hash; 49 | } 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /Nocilla/Matchers/LSMatcheable.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @class LSMatcher; 4 | 5 | @protocol LSMatcheable 6 | 7 | - (LSMatcher *)matcher; 8 | 9 | @end 10 | -------------------------------------------------------------------------------- /Nocilla/Matchers/LSMatcher.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface LSMatcher : NSObject 4 | 5 | - (BOOL)matches:(NSString *)string; 6 | 7 | - (BOOL)matchesData:(NSData *)data; 8 | 9 | @end 10 | -------------------------------------------------------------------------------- /Nocilla/Matchers/LSMatcher.m: -------------------------------------------------------------------------------- 1 | #import "LSMatcher.h" 2 | 3 | @implementation LSMatcher 4 | 5 | - (BOOL)matches:(NSString *)string { 6 | @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"[LSMatcher matches:] is an abstract method" userInfo:nil]; 7 | } 8 | 9 | - (BOOL)matchesData:(NSData *)data { 10 | return [self matches:[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]]; 11 | } 12 | 13 | 14 | #pragma mark - Equality 15 | 16 | - (BOOL)isEqual:(id)object { 17 | @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"[LSMatcher isEqual:] is an abstract method" userInfo:nil]; 18 | } 19 | 20 | - (NSUInteger)hash { 21 | @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"[LSMatcher hash] an abstract method" userInfo:nil]; 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /Nocilla/Matchers/LSRegexMatcher.h: -------------------------------------------------------------------------------- 1 | #import "LSMatcher.h" 2 | 3 | @interface LSRegexMatcher : LSMatcher 4 | 5 | - (instancetype)initWithRegex:(NSRegularExpression *)regex; 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /Nocilla/Matchers/LSRegexMatcher.m: -------------------------------------------------------------------------------- 1 | #import "LSRegexMatcher.h" 2 | 3 | @interface LSRegexMatcher () 4 | @property (nonatomic, strong) NSRegularExpression *regex; 5 | @end 6 | 7 | @implementation LSRegexMatcher 8 | 9 | - (instancetype)initWithRegex:(NSRegularExpression *)regex { 10 | self = [super init]; 11 | if (self) { 12 | _regex = regex; 13 | } 14 | return self; 15 | } 16 | 17 | - (BOOL)matches:(NSString *)string { 18 | return [self.regex numberOfMatchesInString:string options:0 range:NSMakeRange(0, string.length)] > 0; 19 | } 20 | 21 | 22 | #pragma mark - Equality 23 | 24 | - (BOOL)isEqual:(id)object { 25 | if (self == object) { 26 | return YES; 27 | } 28 | 29 | if (![object isKindOfClass:[LSRegexMatcher class]]) { 30 | return NO; 31 | } 32 | 33 | return [self.regex isEqual:((LSRegexMatcher *)object).regex]; 34 | } 35 | 36 | - (NSUInteger)hash { 37 | return self.regex.hash; 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /Nocilla/Matchers/LSStringMatcher.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "LSMatcher.h" 3 | 4 | @interface LSStringMatcher : LSMatcher 5 | 6 | - (instancetype)initWithString:(NSString *)string; 7 | 8 | @end 9 | -------------------------------------------------------------------------------- /Nocilla/Matchers/LSStringMatcher.m: -------------------------------------------------------------------------------- 1 | #import "LSStringMatcher.h" 2 | 3 | @interface LSStringMatcher () 4 | 5 | @property (nonatomic, copy) NSString *string; 6 | 7 | @end 8 | 9 | @implementation LSStringMatcher 10 | 11 | - (instancetype)initWithString:(NSString *)string { 12 | self = [super init]; 13 | if (self) { 14 | _string = string; 15 | } 16 | return self; 17 | } 18 | 19 | - (BOOL)matches:(NSString *)string { 20 | return [self.string isEqualToString:string]; 21 | } 22 | 23 | 24 | #pragma mark - Equality 25 | 26 | - (BOOL)isEqual:(id)object { 27 | if (self == object) { 28 | return YES; 29 | } 30 | 31 | if (![object isKindOfClass:[LSStringMatcher class]]) { 32 | return NO; 33 | } 34 | 35 | return [self.string isEqualToString:((LSStringMatcher *)object).string]; 36 | } 37 | 38 | - (NSUInteger)hash { 39 | return self.string.hash; 40 | } 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /Nocilla/Matchers/NSData+Matcheable.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSData+Matcheable.h 3 | // Nocilla 4 | // 5 | // Created by Luis Solano Bonet on 09/11/14. 6 | // Copyright (c) 2014 Luis Solano Bonet. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "LSMatcheable.h" 11 | 12 | @interface NSData (Matcheable) 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /Nocilla/Matchers/NSData+Matcheable.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSData+Matcheable.m 3 | // Nocilla 4 | // 5 | // Created by Luis Solano Bonet on 09/11/14. 6 | // Copyright (c) 2014 Luis Solano Bonet. All rights reserved. 7 | // 8 | 9 | #import "NSData+Matcheable.h" 10 | #import "LSDataMatcher.h" 11 | 12 | @implementation NSData (Matcheable) 13 | 14 | - (LSMatcher *)matcher { 15 | return [[LSDataMatcher alloc] initWithData:self]; 16 | } 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Nocilla/Matchers/NSRegularExpression+Matcheable.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "LSMatcheable.h" 3 | 4 | @interface NSRegularExpression (Matcheable) 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /Nocilla/Matchers/NSRegularExpression+Matcheable.m: -------------------------------------------------------------------------------- 1 | #import "NSRegularExpression+Matcheable.h" 2 | #import "LSRegexMatcher.h" 3 | 4 | @implementation NSRegularExpression (Matcheable) 5 | 6 | - (LSMatcher *)matcher { 7 | return [[LSRegexMatcher alloc] initWithRegex:self]; 8 | } 9 | 10 | @end 11 | -------------------------------------------------------------------------------- /Nocilla/Matchers/NSString+Matcheable.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "LSMatcheable.h" 3 | 4 | @interface NSString (Matcheable) 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /Nocilla/Matchers/NSString+Matcheable.m: -------------------------------------------------------------------------------- 1 | #import "NSString+Matcheable.h" 2 | #import "LSStringMatcher.h" 3 | 4 | @implementation NSString (Matcheable) 5 | 6 | - (LSMatcher *)matcher { 7 | return [[LSStringMatcher alloc] initWithString:self]; 8 | } 9 | 10 | @end 11 | -------------------------------------------------------------------------------- /Nocilla/Model/LSHTTPBody.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @protocol LSHTTPBody 4 | - (NSData *)data; 5 | @end 6 | -------------------------------------------------------------------------------- /Nocilla/Model/LSHTTPRequest.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @protocol LSHTTPRequest 4 | 5 | @property (nonatomic, strong, readonly) NSURL *url; 6 | @property (nonatomic, strong, readonly) NSString *method; 7 | @property (nonatomic, strong, readonly) NSDictionary *headers; 8 | @property (nonatomic, strong, readonly) NSData *body; 9 | 10 | @end 11 | -------------------------------------------------------------------------------- /Nocilla/Model/LSHTTPResponse.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @protocol LSHTTPResponse 4 | @property (nonatomic, assign, readonly) NSInteger statusCode; 5 | @property (nonatomic, strong, readonly) NSDictionary *headers; 6 | @property (nonatomic, strong, readonly) NSData *body; 7 | @end 8 | -------------------------------------------------------------------------------- /Nocilla/Nocilla-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'Nocilla' target in the 'Nocilla' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /Nocilla/Nocilla.h: -------------------------------------------------------------------------------- 1 | // 2 | // Nocilla.h 3 | // Nocilla 4 | // 5 | // Created by Robert Böhnke on 26/03/15. 6 | // Copyright (c) 2015 Luis Solano Bonet. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for Nocilla. 12 | FOUNDATION_EXPORT double NocillaVersionNumber; 13 | 14 | //! Project version string for Nocilla. 15 | FOUNDATION_EXPORT const unsigned char NocillaVersionString[]; 16 | 17 | #import 18 | #import 19 | #import 20 | #import 21 | #import 22 | #import 23 | #import 24 | #import 25 | #import 26 | #import 27 | #import 28 | -------------------------------------------------------------------------------- /Nocilla/Stubs/LSStubRequest.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "LSStubResponse.h" 3 | #import "LSHTTPRequest.h" 4 | 5 | 6 | @class LSMatcher; 7 | @class LSStubRequest; 8 | @class LSStubResponse; 9 | 10 | @interface LSStubRequest : NSObject 11 | @property (nonatomic, strong, readonly) NSString *method; 12 | @property (nonatomic, strong, readonly) LSMatcher *urlMatcher; 13 | @property (nonatomic, strong, readonly) NSDictionary *headers; 14 | @property (nonatomic, strong, readwrite) LSMatcher *body; 15 | 16 | @property (nonatomic, strong) LSStubResponse *response; 17 | 18 | - (instancetype)initWithMethod:(NSString *)method url:(NSString *)url; 19 | - (instancetype)initWithMethod:(NSString *)method urlMatcher:(LSMatcher *)urlMatcher; 20 | 21 | - (void)setHeader:(NSString *)header value:(NSString *)value; 22 | 23 | - (BOOL)matchesRequest:(id)request; 24 | @end 25 | -------------------------------------------------------------------------------- /Nocilla/Stubs/LSStubRequest.m: -------------------------------------------------------------------------------- 1 | #import "LSStubRequest.h" 2 | #import "LSMatcher.h" 3 | #import "NSString+Matcheable.h" 4 | 5 | @interface LSStubRequest () 6 | @property (nonatomic, strong, readwrite) NSString *method; 7 | @property (nonatomic, strong, readwrite) LSMatcher *urlMatcher; 8 | @property (nonatomic, strong, readwrite) NSMutableDictionary *mutableHeaders; 9 | 10 | -(BOOL)matchesMethod:(id)request; 11 | -(BOOL)matchesURL:(id)request; 12 | -(BOOL)matchesHeaders:(id)request; 13 | -(BOOL)matchesBody:(id)request; 14 | @end 15 | 16 | @implementation LSStubRequest 17 | 18 | - (instancetype)initWithMethod:(NSString *)method url:(NSString *)url { 19 | return [self initWithMethod:method urlMatcher:[url matcher]]; 20 | } 21 | 22 | - (instancetype)initWithMethod:(NSString *)method urlMatcher:(LSMatcher *)urlMatcher; { 23 | self = [super init]; 24 | if (self) { 25 | self.method = method; 26 | self.urlMatcher = urlMatcher; 27 | self.mutableHeaders = [NSMutableDictionary dictionary]; 28 | } 29 | return self; 30 | } 31 | 32 | - (void)setHeader:(NSString *)header value:(NSString *)value { 33 | [self.mutableHeaders setValue:value forKey:header]; 34 | } 35 | 36 | - (NSDictionary *)headers { 37 | return [NSDictionary dictionaryWithDictionary:self.mutableHeaders];; 38 | } 39 | 40 | - (NSString *)description { 41 | return [NSString stringWithFormat:@"StubRequest:\nMethod: %@\nURL: %@\nHeaders: %@\nBody: %@\nResponse: %@", 42 | self.method, 43 | self.urlMatcher, 44 | self.headers, 45 | self.body, 46 | self.response]; 47 | } 48 | 49 | - (LSStubResponse *)response { 50 | if (!_response) { 51 | _response = [[LSStubResponse alloc] initDefaultResponse]; 52 | } 53 | return _response; 54 | 55 | } 56 | 57 | - (BOOL)matchesRequest:(id)request { 58 | if ([self matchesMethod:request] 59 | && [self matchesURL:request] 60 | && [self matchesHeaders:request] 61 | && [self matchesBody:request] 62 | ) { 63 | return YES; 64 | } 65 | return NO; 66 | } 67 | 68 | -(BOOL)matchesMethod:(id)request { 69 | if (!self.method || [self.method isEqualToString:request.method]) { 70 | return YES; 71 | } 72 | return NO; 73 | } 74 | 75 | -(BOOL)matchesURL:(id)request { 76 | return [self.urlMatcher matches:[request.url absoluteString]]; 77 | } 78 | 79 | -(BOOL)matchesHeaders:(id)request { 80 | for (NSString *header in self.headers) { 81 | if (![[request.headers objectForKey:header] isEqualToString:[self.headers objectForKey:header]]) { 82 | return NO; 83 | } 84 | } 85 | return YES; 86 | } 87 | 88 | -(BOOL)matchesBody:(id)request { 89 | NSData *reqBody = request.body; 90 | if (!self.body || [self.body matchesData:reqBody]) { 91 | return YES; 92 | } 93 | return NO; 94 | } 95 | 96 | 97 | #pragma mark - Equality 98 | 99 | - (BOOL)isEqual:(id)object { 100 | if (self == object) { 101 | return YES; 102 | } 103 | 104 | if (![object isKindOfClass:[LSStubRequest class]]) { 105 | return NO; 106 | } 107 | 108 | return [self isEqualToStubRequest:object]; 109 | } 110 | 111 | - (BOOL)isEqualToStubRequest:(LSStubRequest *)stubRequest { 112 | if (!stubRequest) { 113 | return NO; 114 | } 115 | 116 | BOOL methodEqual = [self.method isEqualToString:stubRequest.method]; 117 | BOOL urlMatcherEqual = [self.urlMatcher isEqual:stubRequest.urlMatcher]; 118 | BOOL headersEqual = [self.headers isEqual:stubRequest.headers]; 119 | BOOL bodyEqual = (self.body == nil && stubRequest.body == nil) || [self.body isEqual:stubRequest.body]; 120 | 121 | return methodEqual && urlMatcherEqual && headersEqual && bodyEqual; 122 | } 123 | 124 | - (NSUInteger)hash { 125 | return self.method.hash ^ self.urlMatcher.hash ^ self.headers.hash ^ self.body.hash; 126 | } 127 | 128 | @end 129 | 130 | 131 | -------------------------------------------------------------------------------- /Nocilla/Stubs/LSStubResponse.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "LSHTTPResponse.h" 3 | 4 | @interface LSStubResponse : NSObject 5 | 6 | @property (nonatomic, assign, readonly) NSInteger statusCode; 7 | @property (nonatomic, strong) NSData *body; 8 | @property (nonatomic, strong, readonly) NSDictionary *headers; 9 | 10 | @property (nonatomic, assign, readonly) BOOL shouldFail; 11 | @property (nonatomic, strong, readonly) NSError *error; 12 | 13 | - (id)initWithError:(NSError *)error; 14 | - (id)initWithStatusCode:(NSInteger)statusCode; 15 | - (id)initWithRawResponse:(NSData *)rawResponseData; 16 | - (id)initDefaultResponse; 17 | - (void)setHeader:(NSString *)header value:(NSString *)value; 18 | @end 19 | -------------------------------------------------------------------------------- /Nocilla/Stubs/LSStubResponse.m: -------------------------------------------------------------------------------- 1 | #import "LSStubResponse.h" 2 | 3 | @interface LSStubResponse () 4 | @property (nonatomic, assign, readwrite) NSInteger statusCode; 5 | @property (nonatomic, strong) NSMutableDictionary *mutableHeaders; 6 | @property (nonatomic, assign) UInt64 offset; 7 | @property (nonatomic, assign, getter = isDone) BOOL done; 8 | @property (nonatomic, assign) BOOL shouldFail; 9 | @property (nonatomic, strong) NSError *error; 10 | @end 11 | 12 | @implementation LSStubResponse 13 | 14 | #pragma Initializers 15 | - (id)initDefaultResponse { 16 | self = [super init]; 17 | if (self) { 18 | self.shouldFail = NO; 19 | 20 | self.statusCode = 200; 21 | self.mutableHeaders = [NSMutableDictionary dictionary]; 22 | self.body = [@"" dataUsingEncoding:NSUTF8StringEncoding]; 23 | } 24 | return self; 25 | } 26 | 27 | 28 | - (id)initWithError:(NSError *)error { 29 | self = [super init]; 30 | if (self) { 31 | self.shouldFail = YES; 32 | self.error = error; 33 | } 34 | return self; 35 | } 36 | 37 | -(id)initWithStatusCode:(NSInteger)statusCode { 38 | self = [super init]; 39 | if (self) { 40 | self.shouldFail = NO; 41 | self.statusCode = statusCode; 42 | self.mutableHeaders = [NSMutableDictionary dictionary]; 43 | self.body = [@"" dataUsingEncoding:NSUTF8StringEncoding]; 44 | } 45 | return self; 46 | } 47 | 48 | - (id)initWithRawResponse:(NSData *)rawResponseData { 49 | self = [self initDefaultResponse]; 50 | if (self) { 51 | CFHTTPMessageRef httpMessage = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, FALSE); 52 | if (httpMessage) { 53 | CFHTTPMessageAppendBytes(httpMessage, [rawResponseData bytes], [rawResponseData length]); 54 | 55 | self.body = rawResponseData; // By default 56 | 57 | if (CFHTTPMessageIsHeaderComplete(httpMessage)) { 58 | self.statusCode = (NSInteger)CFHTTPMessageGetResponseStatusCode(httpMessage); 59 | self.mutableHeaders = [NSMutableDictionary dictionaryWithDictionary:(__bridge_transfer NSDictionary *)CFHTTPMessageCopyAllHeaderFields(httpMessage)]; 60 | self.body = (__bridge_transfer NSData *)CFHTTPMessageCopyBody(httpMessage); 61 | } 62 | CFRelease(httpMessage); 63 | } 64 | } 65 | return self; 66 | } 67 | 68 | - (void)setHeader:(NSString *)header value:(NSString *)value { 69 | [self.mutableHeaders setValue:value forKey:header]; 70 | } 71 | - (NSDictionary *)headers { 72 | return [NSDictionary dictionaryWithDictionary:self.mutableHeaders]; 73 | } 74 | 75 | - (NSString *)description { 76 | return [NSString stringWithFormat:@"StubRequest:\nStatus Code: %ld\nHeaders: %@\nBody: %@", 77 | (long)self.statusCode, 78 | self.mutableHeaders, 79 | self.body]; 80 | } 81 | @end 82 | -------------------------------------------------------------------------------- /NocillaTests/DSL/LSHTTPRequestDSLRepresentationSpec.m: -------------------------------------------------------------------------------- 1 | #import "Kiwi.h" 2 | #import "LSTestRequest.h" 3 | #import "LSHTTPRequestDSLRepresentation.h" 4 | 5 | SPEC_BEGIN(LSHTTPRequestDSLRepresentationSpec) 6 | describe(@"description", ^{ 7 | __block LSHTTPRequestDSLRepresentation *dsl = nil; 8 | describe(@"a request with a GET method and url", ^{ 9 | beforeEach(^{ 10 | LSTestRequest *request = [[LSTestRequest alloc] initWithMethod:@"GET" url:@"http://www.google.com"]; 11 | dsl = [[LSHTTPRequestDSLRepresentation alloc] initWithRequest:request]; 12 | }); 13 | it(@"should return the DSL representation", ^{ 14 | [[[dsl description] should] equal:@"stubRequest(@\"GET\", @\"http://www.google.com\");"]; 15 | }); 16 | }); 17 | describe(@"a request with a POST method and a url", ^{ 18 | beforeEach(^{ 19 | LSTestRequest *request = [[LSTestRequest alloc] initWithMethod:@"POST" url:@"http://luissolano.com"]; 20 | dsl = [[LSHTTPRequestDSLRepresentation alloc] initWithRequest:request]; 21 | 22 | }); 23 | it(@"should return the DSL representation", ^{ 24 | [[[dsl description] should] equal:@"stubRequest(@\"POST\", @\"http://luissolano.com\");"]; 25 | }); 26 | }); 27 | describe(@"a request with one header", ^{ 28 | beforeEach(^{ 29 | LSTestRequest *request = [[LSTestRequest alloc] initWithMethod:@"POST" url:@"http://luissolano.com"]; 30 | [request setHeader:@"Accept" value:@"text/plain"]; 31 | dsl = [[LSHTTPRequestDSLRepresentation alloc] initWithRequest:request]; 32 | }); 33 | it(@"should return the DSL representation", ^{ 34 | [[[dsl description] should] equal:@"stubRequest(@\"POST\", @\"http://luissolano.com\").\nwithHeaders(@{ @\"Accept\": @\"text/plain\" });"]; 35 | }); 36 | }); 37 | describe(@"a request with 3 headers", ^{ 38 | beforeEach(^{ 39 | LSTestRequest *request = [[LSTestRequest alloc] initWithMethod:@"POST" url:@"http://luissolano.com"]; 40 | [request setHeader:@"Accept" value:@"text/plain"]; 41 | [request setHeader:@"Content-Length" value:@"18"]; 42 | [request setHeader:@"If-Match" value:@"a8fhw0dhasd03qn02"]; 43 | dsl = [[LSHTTPRequestDSLRepresentation alloc] initWithRequest:request]; 44 | 45 | }); 46 | it(@"should return the DSL representation", ^{ 47 | NSString *expected = @"stubRequest(@\"POST\", @\"http://luissolano.com\").\nwithHeaders(@{ @\"Accept\": @\"text/plain\", @\"Content-Length\": @\"18\", @\"If-Match\": @\"a8fhw0dhasd03qn02\" });"; 48 | [[[dsl description] should] equal:expected]; 49 | }); 50 | }); 51 | describe(@"when a header contains a double quoute", ^{ 52 | beforeEach(^{ 53 | LSTestRequest *request = [[LSTestRequest alloc] initWithMethod:@"POST" url:@"http://luissolano.com"]; 54 | [request setHeader:@"X-MY-HEADER" value:@"quote\"quoute"]; 55 | dsl = [[LSHTTPRequestDSLRepresentation alloc] initWithRequest:request]; 56 | }); 57 | it(@"should escape the result", ^{ 58 | [[[dsl description] should] equal:@"stubRequest(@\"POST\", @\"http://luissolano.com\").\nwithHeaders(@{ @\"X-MY-HEADER\": @\"quote\"quoute\" });"]; 59 | }); 60 | }); 61 | describe(@"a request with headers and body", ^{ 62 | beforeEach(^{ 63 | LSTestRequest *request = [[LSTestRequest alloc] initWithMethod:@"POST" url:@"http://luissolano.com"]; 64 | [request setHeader:@"Accept" value:@"text/plain"]; 65 | [request setHeader:@"Content-Length" value:@"18"]; 66 | [request setHeader:@"If-Match" value:@"a8fhw0dhasd03qn02"]; 67 | [request setBody:[@"The body of a request, yeah!" dataUsingEncoding:NSUTF8StringEncoding]]; 68 | dsl = [[LSHTTPRequestDSLRepresentation alloc] initWithRequest:request]; 69 | 70 | }); 71 | it(@"should return the DSL representation", ^{ 72 | NSString *expected = @"stubRequest(@\"POST\", @\"http://luissolano.com\").\nwithHeaders(@{ @\"Accept\": @\"text/plain\", @\"Content-Length\": @\"18\", @\"If-Match\": @\"a8fhw0dhasd03qn02\" }).\nwithBody(@\"The body of a request, yeah!\");"; 73 | [[[dsl description] should] equal:expected]; 74 | }); 75 | }); 76 | context(@"when the body contain double quotes", ^{ 77 | beforeEach(^{ 78 | LSTestRequest *request = [[LSTestRequest alloc] initWithMethod:@"POST" url:@"http://luissolano.com"]; 79 | [request setBody:[@"{\"text\":\"adios\"}" dataUsingEncoding:NSUTF8StringEncoding]]; 80 | dsl = [[LSHTTPRequestDSLRepresentation alloc] initWithRequest:request]; 81 | }); 82 | it(@"should return the DSL representation", ^{ 83 | NSString *expected = @"stubRequest(@\"POST\", @\"http://luissolano.com\").\nwithBody(@\"{\\\"text\\\":\\\"adios\\\"}\");"; 84 | [[[dsl description] should] equal:expected]; 85 | }); 86 | }); 87 | }); 88 | SPEC_END -------------------------------------------------------------------------------- /NocillaTests/Diff/LSHTTPRequestDiffSpec.m: -------------------------------------------------------------------------------- 1 | #import "Kiwi.h" 2 | #import "LSHTTPRequest.h" 3 | #import "LSHTTPRequestDiff.h" 4 | #import "LSTestRequest.h" 5 | 6 | SPEC_BEGIN(LSHTTPRequestDiffSpec) 7 | describe(@"diffing two LSHTTPRequests", ^{ 8 | __block LSTestRequest *oneRequest = nil; 9 | __block LSTestRequest *anotherRequest = nil; 10 | __block LSHTTPRequestDiff *diff = nil; 11 | context(@"when both represent the same request", ^{ 12 | beforeEach(^{ 13 | NSString *urlString = @"http://www.google.com"; 14 | oneRequest = [[LSTestRequest alloc] initWithMethod:@"GET" url:urlString]; 15 | anotherRequest = [[LSTestRequest alloc] initWithMethod:@"GET" url:urlString]; 16 | diff = [[LSHTTPRequestDiff alloc] initWithRequest:oneRequest andRequest:anotherRequest]; 17 | }); 18 | 19 | it(@"should result in an empty diff", ^{ 20 | [[theValue(diff.isEmpty) should] beYes]; 21 | }); 22 | it(@"should an empty description", ^{ 23 | [[[diff description] should] equal:@""]; 24 | }); 25 | }); 26 | context(@"when the request differ in the method", ^{ 27 | beforeEach(^{ 28 | NSString *urlString = @"http://www.google.com"; 29 | oneRequest = [[LSTestRequest alloc] initWithMethod:@"GET" url:urlString]; 30 | anotherRequest = [[LSTestRequest alloc] initWithMethod:@"POST" url:urlString]; 31 | }); 32 | it(@"should not be empty", ^{ 33 | diff = [[LSHTTPRequestDiff alloc] initWithRequest:oneRequest andRequest:anotherRequest]; 34 | [[theValue(diff.isEmpty) should] beNo]; 35 | }); 36 | context(@"in one direction", ^{ 37 | beforeEach(^{ 38 | diff = [[LSHTTPRequestDiff alloc] initWithRequest:oneRequest andRequest:anotherRequest]; 39 | }); 40 | it(@"should have a description representing the diff", ^{ 41 | NSString *expected = @"- Method: GET\n+ Method: POST\n"; 42 | [[[diff description] should] equal:expected]; 43 | }); 44 | 45 | }); 46 | context(@"in the other direction", ^{ 47 | beforeEach(^{ 48 | diff = [[LSHTTPRequestDiff alloc] initWithRequest:anotherRequest andRequest:oneRequest]; 49 | }); 50 | it(@"should have a description representing the diff", ^{ 51 | NSString *expected = @"- Method: POST\n+ Method: GET\n"; 52 | [[[diff description] should] equal:expected]; 53 | }); 54 | 55 | }); 56 | }); 57 | 58 | context(@"when the requests differ in the URL", ^{ 59 | beforeEach(^{ 60 | oneRequest = [[LSTestRequest alloc] initWithMethod:@"GET" url:@"http://www.google.com"]; 61 | anotherRequest = [[LSTestRequest alloc] initWithMethod:@"GET" url:@"http://www.luissolano.com"]; 62 | }); 63 | it(@"should not be empty", ^{ 64 | diff = [[LSHTTPRequestDiff alloc] initWithRequest:oneRequest andRequest:anotherRequest]; 65 | [[theValue(diff.isEmpty) should] beNo]; 66 | }); 67 | context(@"in one direction", ^{ 68 | beforeEach(^{ 69 | diff = [[LSHTTPRequestDiff alloc] initWithRequest:oneRequest andRequest:anotherRequest]; 70 | }); 71 | it(@"should have a description representing the diff", ^{ 72 | NSString *expected = @"- URL: http://www.google.com\n+ URL: http://www.luissolano.com\n"; 73 | [[[diff description] should] equal:expected]; 74 | }); 75 | }); 76 | context(@"in the other direction", ^{ 77 | beforeEach(^{ 78 | diff = [[LSHTTPRequestDiff alloc] initWithRequest:anotherRequest andRequest:oneRequest]; 79 | }); 80 | it(@"should have a description representing the diff", ^{ 81 | NSString *expected = @"- URL: http://www.luissolano.com\n+ URL: http://www.google.com\n"; 82 | [[[diff description] should] equal:expected]; 83 | }); 84 | }); 85 | }); 86 | context(@"when the request differ in one header", ^{ 87 | beforeEach(^{ 88 | oneRequest = [[LSTestRequest alloc] initWithMethod:@"GET" url:@"http://www.google.com"]; 89 | [oneRequest setHeader:@"Content-Type" value:@"application/json"]; 90 | anotherRequest = [[LSTestRequest alloc] initWithMethod:@"GET" url:@"http://www.google.com"]; 91 | }); 92 | it(@"should not be empty", ^{ 93 | diff = [[LSHTTPRequestDiff alloc] initWithRequest:oneRequest andRequest:anotherRequest]; 94 | [[theValue(diff.isEmpty) should] beNo]; 95 | }); 96 | context(@"in one direction", ^{ 97 | beforeEach(^{ 98 | diff = [[LSHTTPRequestDiff alloc] initWithRequest:oneRequest andRequest:anotherRequest]; 99 | }); 100 | it(@"should have a description representing the diff", ^{ 101 | NSString *expected = @" Headers:\n-\t\"Content-Type\": \"application/json\"\n"; 102 | [[[diff description] should] equal:expected]; 103 | }); 104 | 105 | }); 106 | context(@"in the other direction", ^{ 107 | beforeEach(^{ 108 | diff = [[LSHTTPRequestDiff alloc] initWithRequest:anotherRequest andRequest:oneRequest]; 109 | }); 110 | it(@"should have a description representing the diff", ^{ 111 | NSString *expected = @" Headers:\n+\t\"Content-Type\": \"application/json\"\n"; 112 | [[[diff description] should] equal:expected]; 113 | }); 114 | 115 | }); 116 | 117 | }); 118 | 119 | context(@"when the request differ in one header each", ^{ 120 | beforeEach(^{ 121 | oneRequest = [[LSTestRequest alloc] initWithMethod:@"GET" url:@"http://www.google.com"]; 122 | [oneRequest setHeader:@"Content-Type" value:@"application/json"]; 123 | anotherRequest = [[LSTestRequest alloc] initWithMethod:@"GET" url:@"http://www.google.com"]; 124 | [anotherRequest setHeader:@"Accept" value:@"text/plain"]; 125 | }); 126 | it(@"should not be empty", ^{ 127 | diff = [[LSHTTPRequestDiff alloc] initWithRequest:oneRequest andRequest:anotherRequest]; 128 | [[theValue(diff.isEmpty) should] beNo]; 129 | }); 130 | context(@"in one direction", ^{ 131 | beforeEach(^{ 132 | diff = [[LSHTTPRequestDiff alloc] initWithRequest:oneRequest andRequest:anotherRequest]; 133 | }); 134 | it(@"should have a description representing the diff", ^{ 135 | NSString *expected = @" Headers:\n-\t\"Content-Type\": \"application/json\"\n+\t\"Accept\": \"text/plain\"\n"; 136 | [[[diff description] should] equal:expected]; 137 | }); 138 | }); 139 | context(@"in the other direction", ^{ 140 | beforeEach(^{ 141 | diff = [[LSHTTPRequestDiff alloc] initWithRequest:anotherRequest andRequest:oneRequest]; 142 | }); 143 | it(@"should have a description representing the diff", ^{ 144 | NSString *expected = @" Headers:\n-\t\"Accept\": \"text/plain\"\n+\t\"Content-Type\": \"application/json\"\n"; 145 | [[[diff description] should] equal:expected]; 146 | }); 147 | 148 | }); 149 | }); 150 | context(@"when the requests differ in the body", ^{ 151 | beforeEach(^{ 152 | oneRequest = [[LSTestRequest alloc] initWithMethod:@"GET" url:@"http://www.google.com"]; 153 | oneRequest.body = [@"this is a body" dataUsingEncoding:NSUTF8StringEncoding]; 154 | anotherRequest = [[LSTestRequest alloc] initWithMethod:@"GET" url:@"http://www.google.com"]; 155 | }); 156 | it(@"should not be empty", ^{ 157 | diff = [[LSHTTPRequestDiff alloc] initWithRequest:oneRequest andRequest:anotherRequest]; 158 | [[theValue(diff.isEmpty) should] beNo]; 159 | }); 160 | context(@"in one direction", ^{ 161 | beforeEach(^{ 162 | diff = [[LSHTTPRequestDiff alloc] initWithRequest:oneRequest andRequest:anotherRequest]; 163 | }); 164 | it(@"should have a description representing the diff", ^{ 165 | NSString *expected = @"- Body: \"this is a body\"\n"; 166 | [[[diff description] should] equal:expected]; 167 | }); 168 | }); 169 | context(@"in the other direction", ^{ 170 | beforeEach(^{ 171 | diff = [[LSHTTPRequestDiff alloc] initWithRequest:anotherRequest andRequest:oneRequest]; 172 | }); 173 | it(@"should have a description representing the diff", ^{ 174 | NSString *expected = @"+ Body: \"this is a body\"\n"; 175 | 176 | [[[diff description] should] equal:expected]; 177 | }); 178 | 179 | }); 180 | }); 181 | context(@"when the requests differ in the Method and the URL", ^{ 182 | beforeEach(^{ 183 | oneRequest = [[LSTestRequest alloc] initWithMethod:@"GET" url:@"http://www.google.com"]; 184 | anotherRequest = [[LSTestRequest alloc] initWithMethod:@"PUT" url:@"http://www.luissolano.com"]; 185 | }); 186 | context(@"in one direction", ^{ 187 | beforeEach(^{ 188 | diff = [[LSHTTPRequestDiff alloc] initWithRequest:oneRequest andRequest:anotherRequest]; 189 | }); 190 | it(@"should have a description representing the diff", ^{ 191 | NSString *expected = @"- Method: GET\n+ Method: PUT\n- URL: http://www.google.com\n+ URL: http://www.luissolano.com\n"; 192 | [[[diff description] should] equal:expected]; 193 | }); 194 | }); 195 | context(@"in the other direction", ^{ 196 | beforeEach(^{ 197 | diff = [[LSHTTPRequestDiff alloc] initWithRequest:anotherRequest andRequest:oneRequest]; 198 | }); 199 | it(@"should have a description representing the diff", ^{ 200 | NSString *expected = @"- Method: PUT\n+ Method: GET\n- URL: http://www.luissolano.com\n+ URL: http://www.google.com\n"; 201 | [[[diff description] should] equal:expected]; 202 | }); 203 | 204 | }); 205 | }); 206 | context(@"when the request differ in the Method, URL and headers", ^{ 207 | beforeEach(^{ 208 | oneRequest = [[LSTestRequest alloc] initWithMethod:@"POST" url:@"http://www.google.es"]; 209 | [oneRequest setHeader:@"X-API-TOKEN" value:@"123456789"]; 210 | [oneRequest setHeader:@"Accept" value:@"application/json"]; 211 | [oneRequest setHeader:@"X-Custom-Header" value:@"Really??"]; 212 | anotherRequest = [[LSTestRequest alloc] initWithMethod:@"DELETE" url:@"http://www.luissolano.com/ispellchecker/"]; 213 | [anotherRequest setHeader:@"Accept" value:@"application/json"]; 214 | [anotherRequest setHeader:@"X-API-TOKEN" value:@"abcedfghi"]; 215 | [anotherRequest setHeader:@"X-APP-ID" value:@"Nocilla"]; 216 | }); 217 | context(@"in one direction", ^{ 218 | beforeEach(^{ 219 | diff = [[LSHTTPRequestDiff alloc] initWithRequest:oneRequest andRequest:anotherRequest]; 220 | }); 221 | it(@"should have a description representing the diff", ^{ 222 | NSString *expected = @"- Method: POST\n+ Method: DELETE\n- URL: http://www.google.es\n+ URL: http://www.luissolano.com/ispellchecker/\n Headers:\n-\t\"X-API-TOKEN\": \"123456789\"\n-\t\"X-Custom-Header\": \"Really??\"\n+\t\"X-API-TOKEN\": \"abcedfghi\"\n+\t\"X-APP-ID\": \"Nocilla\"\n"; 223 | [[[diff description] should] equal:expected]; 224 | }); 225 | }); 226 | context(@"in the other direction", ^{ 227 | beforeEach(^{ 228 | diff = [[LSHTTPRequestDiff alloc] initWithRequest:anotherRequest andRequest:oneRequest]; 229 | }); 230 | it(@"should have a description representing the diff", ^{ 231 | NSString *expected = @"- Method: DELETE\n+ Method: POST\n- URL: http://www.luissolano.com/ispellchecker/\n+ URL: http://www.google.es\n Headers:\n-\t\"X-API-TOKEN\": \"abcedfghi\"\n-\t\"X-APP-ID\": \"Nocilla\"\n+\t\"X-API-TOKEN\": \"123456789\"\n+\t\"X-Custom-Header\": \"Really??\"\n"; 232 | [[[diff description] should] equal:expected]; 233 | }); 234 | }); 235 | }); 236 | context(@"when the requests differ in everything", ^{ 237 | beforeEach(^{ 238 | oneRequest = [[LSTestRequest alloc] initWithMethod:@"PUT" url:@"https://www.google.it"]; 239 | [oneRequest setHeader:@"X-API-TOKEN" value:@"123456789"]; 240 | [oneRequest setHeader:@"Accept" value:@"application/json"]; 241 | [oneRequest setHeader:@"X-Custom-Header" value:@"Really??"]; 242 | [oneRequest setBody:[@"This is a body" dataUsingEncoding:NSUTF8StringEncoding]]; 243 | anotherRequest = [[LSTestRequest alloc] initWithMethod:@"GET" url:@"http://www.luissolano.com"]; 244 | [anotherRequest setHeader:@"X-API-TOKEN" value:@"123456789"]; 245 | [anotherRequest setHeader:@"X-APP-ID" value:@"Nocilla"]; 246 | [anotherRequest setBody:[@"This is THE body" dataUsingEncoding:NSUTF8StringEncoding]]; 247 | }); 248 | context(@"in one direction", ^{ 249 | beforeEach(^{ 250 | diff = [[LSHTTPRequestDiff alloc] initWithRequest:oneRequest andRequest:anotherRequest]; 251 | }); 252 | it(@"should have a description representing the diff", ^{ 253 | NSString *expected = @"- Method: PUT\n+ Method: GET\n- URL: https://www.google.it\n+ URL: http://www.luissolano.com\n Headers:\n-\t\"Accept\": \"application/json\"\n-\t\"X-Custom-Header\": \"Really??\"\n+\t\"X-APP-ID\": \"Nocilla\"\n- Body: \"This is a body\"\n+ Body: \"This is THE body\"\n"; 254 | [[[diff description] should] equal:expected]; 255 | }); 256 | }); 257 | context(@"in the other direction", ^{ 258 | beforeEach(^{ 259 | diff = [[LSHTTPRequestDiff alloc] initWithRequest:anotherRequest andRequest:oneRequest]; 260 | }); 261 | it(@"should have a description representing the diff", ^{ 262 | NSString *expected = @"- Method: GET\n+ Method: PUT\n- URL: http://www.luissolano.com\n+ URL: https://www.google.it\n Headers:\n-\t\"X-APP-ID\": \"Nocilla\"\n+\t\"Accept\": \"application/json\"\n+\t\"X-Custom-Header\": \"Really??\"\n- Body: \"This is THE body\"\n+ Body: \"This is a body\"\n"; 263 | [[[diff description] should] equal:expected]; 264 | }); 265 | }); 266 | }); 267 | }); 268 | SPEC_END -------------------------------------------------------------------------------- /NocillaTests/Hooks/ASIHTTPRequest/ASIHTTPRequestStubbingSpec.m: -------------------------------------------------------------------------------- 1 | #import "Kiwi.h" 2 | #import "Nocilla.h" 3 | #import "ASIHTTPRequest.h" 4 | #import "ASIFormDataRequest.h" 5 | #import "LSASIHTTPRequestHook.h" 6 | 7 | SPEC_BEGIN(ASIHTTPRequestStubbingSpec) 8 | 9 | beforeEach(^{ 10 | [[LSNocilla sharedInstance] start]; 11 | }); 12 | 13 | afterEach(^{ 14 | [[LSNocilla sharedInstance] stop]; 15 | [[LSNocilla sharedInstance] clearStubs]; 16 | }); 17 | 18 | it(@"stubs a GET request", ^{ 19 | stubRequest(@"GET", @"http://httpstat.us/400"). 20 | andReturn(201). 21 | withHeaders(@{@"Header 1":@"Foo", @"Header 2":@"Bar"}). 22 | withBody(@"Holaa!"); 23 | 24 | ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://httpstat.us/400"]]; 25 | 26 | [request startAsynchronous]; 27 | 28 | [request waitUntilFinished]; 29 | 30 | [[expectFutureValue(theValue(request.responseStatusCode)) shouldEventually] equal:theValue(201)]; 31 | [[request.responseString should] equal:@"Holaa!"]; 32 | [[request.responseData should] equal:[@"Holaa!" dataUsingEncoding:NSUTF8StringEncoding]]; 33 | [[request.responseHeaders should] equal:@{@"Header 1":@"Foo", @"Header 2":@"Bar"}]; 34 | [[theValue(request.isFinished) should] beYes]; 35 | 36 | }); 37 | 38 | it(@"stubs a POST request", ^{ 39 | stubRequest(@"POST", @"http://api.example.com/v1/dogs.json"). 40 | withHeaders(@{@"Authorization":@"Bearer 123123123"}). 41 | withBody(@"{\"dog\":\"pepu\"}"). 42 | andReturn(201). 43 | withHeaders(@{@"Header 1":@"Foo", @"Header 2":@"Bar"}). 44 | withBody(@"Holaa!"); 45 | 46 | ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://api.example.com/v1/dogs.json"]]; 47 | request.requestMethod = @"POST"; 48 | [request addRequestHeader:@"Authorization" value:@"Bearer 123123123"]; 49 | [request appendPostData:[@"{\"dog\":\"pepu\"}" dataUsingEncoding:NSUTF8StringEncoding]]; 50 | 51 | [request startAsynchronous]; 52 | 53 | [request waitUntilFinished]; 54 | 55 | [[expectFutureValue(theValue(request.responseStatusCode)) shouldEventually] equal:theValue(201)]; 56 | [[request.responseString should] equal:@"Holaa!"]; 57 | [[request.responseData should] equal:[@"Holaa!" dataUsingEncoding:NSUTF8StringEncoding]]; 58 | [[request.responseHeaders should] equal:@{@"Header 1":@"Foo", @"Header 2":@"Bar"}]; 59 | [[theValue(request.isFinished) should] beYes]; 60 | }); 61 | 62 | it(@"fails a request", ^{ 63 | NSError *error = [NSError nullMock]; 64 | stubRequest(@"POST", @"http://api.example.com/v1/cats"). 65 | withHeaders(@{@"Authorization":@"Basic 667788"}). 66 | withBody(@"name=calcetines&color=black"). 67 | andFailWithError(error); 68 | 69 | ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:[NSURL URLWithString:@"http://api.example.com/v1/cats"]]; 70 | [request setRequestMethod:@"POST"]; 71 | [request addRequestHeader:@"Authorization" value:@"Basic 667788"]; 72 | [request addPostValue:@"calcetines" forKey:@"name"]; 73 | [request addPostValue:@"black" forKey:@"color"]; 74 | 75 | [request startAsynchronous]; 76 | 77 | [request waitUntilFinished]; 78 | 79 | [[expectFutureValue(request.error) shouldEventually] beIdenticalTo:error]; 80 | [[theValue(request.isFinished) should] beYes]; 81 | }); 82 | 83 | it(@"stubs an HTTPS request", ^{ 84 | stubRequest(@"GET", @"https://example.com/things"). 85 | andReturn(201). 86 | withHeaders(@{@"Header 1":@"Foo", @"Header 2":@"Bar"}). 87 | withBody(@"Holaa!"); 88 | 89 | ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"https://example.com/things"]]; 90 | 91 | [request startAsynchronous]; 92 | 93 | [request waitUntilFinished]; 94 | 95 | [[expectFutureValue(theValue(request.responseStatusCode)) shouldEventually] equal:theValue(201)]; 96 | [[request.responseString should] equal:@"Holaa!"]; 97 | [[request.responseData should] equal:[@"Holaa!" dataUsingEncoding:NSUTF8StringEncoding]]; 98 | [[request.responseHeaders should] equal:@{@"Header 1":@"Foo", @"Header 2":@"Bar"}]; 99 | [[theValue(request.isFinished) should] beYes]; 100 | }); 101 | 102 | it(@"stubs a ASIFormDataRequest", ^{ 103 | stubRequest(@"POST", @"http://api.example.com/v1/cats"). 104 | withHeaders(@{@"Authorization":@"Basic 667788"}). 105 | withBody(@"name=calcetines&color=black"). 106 | andReturn(201). 107 | withHeaders(@{@"Header 1":@"Foo", @"Header 2":@"Bar"}). 108 | withBody(@"Holaa!"); 109 | 110 | ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:[NSURL URLWithString:@"http://api.example.com/v1/cats"]]; 111 | [request setRequestMethod:@"POST"]; 112 | [request addRequestHeader:@"Authorization" value:@"Basic 667788"]; 113 | [request addPostValue:@"calcetines" forKey:@"name"]; 114 | [request addPostValue:@"black" forKey:@"color"]; 115 | 116 | 117 | [request startAsynchronous]; 118 | 119 | [request waitUntilFinished]; 120 | 121 | [[expectFutureValue(theValue(request.responseStatusCode)) shouldEventually] equal:theValue(201)]; 122 | [[request.responseString should] equal:@"Holaa!"]; 123 | [[request.responseData should] equal:[@"Holaa!" dataUsingEncoding:NSUTF8StringEncoding]]; 124 | [[request.responseHeaders should] equal:@{@"Header 1":@"Foo", @"Header 2":@"Bar"}]; 125 | [[theValue(request.isFinished) should] beYes]; 126 | }); 127 | 128 | SPEC_END -------------------------------------------------------------------------------- /NocillaTests/Hooks/ASIHTTPRequest/LSASIHTTPRequestHookSpec.m: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luisobo/Nocilla/3a7fabeade4cd3329d3988728eee0416e6c689de/NocillaTests/Hooks/ASIHTTPRequest/LSASIHTTPRequestHookSpec.m -------------------------------------------------------------------------------- /NocillaTests/Hooks/NSURLRequest/AFNetworkingStubbingSpec.m: -------------------------------------------------------------------------------- 1 | #import "Kiwi.h" 2 | #import "AFNetworking.h" 3 | #import "Nocilla.h" 4 | #import "HTTPServer.h" 5 | #import "LSTestingConnection.h" 6 | 7 | SPEC_BEGIN(AFNetworkingStubbingSpec) 8 | 9 | beforeEach(^{ 10 | [[LSNocilla sharedInstance] start]; 11 | }); 12 | afterEach(^{ 13 | [[LSNocilla sharedInstance] stop]; 14 | [[LSNocilla sharedInstance] clearStubs]; 15 | }); 16 | 17 | context(@"AFNetworking", ^{ 18 | it(@"should stub the request", ^{ 19 | stubRequest(@"POST", @"https://example.com/say-hello"). 20 | withHeader(@"Content-Type", @"text/plain"). 21 | withHeader(@"X-MY-AWESOME-HEADER", @"sisisi"). 22 | withBody(@"Adios!"). 23 | andReturn(200). 24 | withHeader(@"Content-Type", @"text/plain"). 25 | withBody(@"hola"); 26 | 27 | NSURL *url = [NSURL URLWithString:@"https://example.com/say-hello"]; 28 | NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; 29 | [request setHTTPMethod:@"POST"]; 30 | [request setValue:@"text/plain" forHTTPHeaderField:@"Content-Type"]; 31 | [request setValue:@"sisisi" forHTTPHeaderField:@"X-MY-AWESOME-HEADER"]; 32 | [request setHTTPBody:[@"Adios!" dataUsingEncoding:NSASCIIStringEncoding]]; 33 | AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request]; 34 | [operation start]; 35 | 36 | [operation waitUntilFinished]; 37 | 38 | [operation.error shouldBeNil]; 39 | [[operation.responseString should] equal:@"hola"]; 40 | [[theValue(operation.response.statusCode) should] equal:theValue(200)]; 41 | [[[operation.response.allHeaderFields objectForKey:@"Content-Type"] should] equal:@"text/plain"]; 42 | }); 43 | 44 | it(@"should stub the request with a raw reponse", ^{ 45 | stubRequest(@"POST", @"https://example.com/say-hello"). 46 | withHeader(@"Content-Type", @"text/plain"). 47 | withHeader(@"X-MY-AWESOME-HEADER", @"sisisi"). 48 | withBody(@"Adios!"). 49 | andReturnRawResponse([@"HTTP/1.1 200 OK\nContent-Type: text/plain\n\nhola" dataUsingEncoding:NSUTF8StringEncoding]); 50 | 51 | NSURL *url = [NSURL URLWithString:@"https://example.com/say-hello"]; 52 | NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; 53 | [request setHTTPMethod:@"POST"]; 54 | [request setValue:@"text/plain" forHTTPHeaderField:@"Content-Type"]; 55 | [request setValue:@"sisisi" forHTTPHeaderField:@"X-MY-AWESOME-HEADER"]; 56 | [request setHTTPBody:[@"Adios!" dataUsingEncoding:NSASCIIStringEncoding]]; 57 | AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request]; 58 | [operation start]; 59 | 60 | [operation waitUntilFinished]; 61 | 62 | [operation.error shouldBeNil]; 63 | [[operation.responseString should] equal:@"hola"]; 64 | [[theValue(operation.response.statusCode) should] equal:theValue(200)]; 65 | [[[operation.response.allHeaderFields objectForKey:@"Content-Type"] should] equal:@"text/plain"]; 66 | }); 67 | 68 | it(@"should have the same result as a real HTTP request", ^{ 69 | NSURL *url = [NSURL URLWithString:@"http://localhost:12345/say-hello"]; 70 | 71 | [[LSNocilla sharedInstance] stop]; 72 | 73 | HTTPServer *server = [[HTTPServer alloc] init]; 74 | [server setPort:12345]; 75 | NSError *error; 76 | [server setConnectionClass:[LSTestingConnection class]]; 77 | [server start:&error]; 78 | [error shouldBeNil]; 79 | 80 | NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; 81 | [request setHTTPMethod:@"POST"]; 82 | [request setValue:@"text/plain" forHTTPHeaderField:@"Content-Type"]; 83 | [request setValue:@"sisisi" forHTTPHeaderField:@"X-MY-AWESOME-HEADER"]; 84 | [request setHTTPBody:[@"Adios!" dataUsingEncoding:NSASCIIStringEncoding]]; 85 | AFHTTPRequestOperation *realOperation = [[AFHTTPRequestOperation alloc] initWithRequest:request]; 86 | [realOperation start]; 87 | 88 | [realOperation waitUntilFinished]; 89 | 90 | [realOperation.error shouldBeNil]; 91 | [[realOperation.responseString should] equal:@"hola"]; 92 | [[theValue(realOperation.response.statusCode) should] equal:theValue(200)]; 93 | 94 | NSHTTPURLResponse *realResponse = realOperation.response; 95 | NSString *realBody = realOperation.responseString; 96 | 97 | [server stop]; 98 | 99 | [[LSNocilla sharedInstance] start]; 100 | 101 | stubRequest(@"POST", @"http://localhost:12345/say-hello"). 102 | withHeaders(@{@"Content-Type" : @"text/plain", @"X-MY-AWESOME-HEADER" : @"sisisi"}). 103 | withBody(@"Adios!"). 104 | andReturn(200). 105 | withHeaders([realResponse allHeaderFields]). 106 | withBody(@"hola"); 107 | 108 | 109 | 110 | NSMutableURLRequest *stubbedRequest = [NSMutableURLRequest requestWithURL:url]; 111 | [stubbedRequest setHTTPMethod:@"POST"]; 112 | [stubbedRequest setValue:@"text/plain" forHTTPHeaderField:@"Content-Type"]; 113 | [stubbedRequest setValue:@"sisisi" forHTTPHeaderField:@"X-MY-AWESOME-HEADER"]; 114 | [stubbedRequest setHTTPBody:[@"Adios!" dataUsingEncoding:NSASCIIStringEncoding]]; 115 | AFHTTPRequestOperation *stubbedOperation = [[AFHTTPRequestOperation alloc] initWithRequest:stubbedRequest]; 116 | [stubbedOperation start]; 117 | 118 | [stubbedOperation waitUntilFinished]; 119 | 120 | [stubbedOperation.error shouldBeNil]; 121 | [[stubbedOperation.responseString should] equal:@"hola"]; 122 | [[theValue(stubbedOperation.response.statusCode) should] equal:theValue(200)]; 123 | 124 | NSHTTPURLResponse *stubbedResponse = stubbedOperation.response; 125 | NSString *stubbedBody = stubbedOperation.responseString; 126 | 127 | 128 | NSDictionary *realHeaders = [realResponse allHeaderFields]; 129 | NSDictionary *stubbedHeaders = [stubbedResponse allHeaderFields]; 130 | [[theValue(realResponse.statusCode) should] equal:theValue(stubbedResponse.statusCode)]; 131 | [[realHeaders should] equal:stubbedHeaders]; 132 | [[realBody should] equal:stubbedBody]; 133 | }); 134 | 135 | it(@"should stub an asynchronous request", ^{ 136 | stubRequest(@"POST", @"https://example.com/say-hello"). 137 | withHeaders(@{ @"X-MY-AWESOME-HEADER": @"sisisi", @"Content-Type": @"text/plain" }). 138 | withBody(@"Adios!"). 139 | andReturn(200). 140 | withHeader(@"Content-Type", @"text/plain"). 141 | withBody(@"hola"); 142 | 143 | NSURL *url = [NSURL URLWithString:@"https://example.com/say-hello"]; 144 | NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; 145 | [request setHTTPMethod:@"POST"]; 146 | [request setValue:@"text/plain" forHTTPHeaderField:@"Content-Type"]; 147 | [request setValue:@"sisisi" forHTTPHeaderField:@"X-MY-AWESOME-HEADER"]; 148 | [request setHTTPBody:[@"Adios!" dataUsingEncoding:NSASCIIStringEncoding]]; 149 | AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request]; 150 | [operation start]; 151 | 152 | [[expectFutureValue(theValue(operation.response.statusCode)) shouldEventually] equal:theValue(200)]; 153 | [[operation.responseString should] equal:@"hola"]; 154 | 155 | [[[operation.response.allHeaderFields objectForKey:@"Content-Type"] shouldEventually] equal:@"text/plain"]; 156 | [operation.error shouldBeNil]; 157 | }); 158 | 159 | it(@"stubs a request with an error", ^{ 160 | NSError *error = [NSError errorWithDomain:@"com.luisobo.nocilla" code:123 userInfo:@{NSLocalizedDescriptionKey:@"Failing, failing... 1, 2, 3..."}]; 161 | 162 | stubRequest(@"POST", @"https://example.com/say-hello"). 163 | withHeaders(@{ @"X-MY-AWESOME-HEADER": @"sisisi", @"Content-Type": @"text/plain" }). 164 | withBody(@"Adios!"). 165 | andFailWithError(error); 166 | 167 | NSURL *url = [NSURL URLWithString:@"https://example.com/say-hello"]; 168 | NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; 169 | [request setHTTPMethod:@"POST"]; 170 | [request setValue:@"text/plain" forHTTPHeaderField:@"Content-Type"]; 171 | [request setValue:@"sisisi" forHTTPHeaderField:@"X-MY-AWESOME-HEADER"]; 172 | [request setHTTPBody:[@"Adios!" dataUsingEncoding:NSASCIIStringEncoding]]; 173 | AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request]; 174 | 175 | 176 | __block BOOL succeed = NO; 177 | __block BOOL failed = NO; 178 | __block NSError *capturedError = nil; 179 | [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) { 180 | succeed = YES; 181 | } failure:^(AFHTTPRequestOperation *operation, NSError *error) { 182 | capturedError = error; 183 | failed = YES; 184 | }]; 185 | 186 | [operation start]; 187 | 188 | [[expectFutureValue(theValue(failed)) shouldEventually] beYes]; 189 | [[capturedError should] equal:[NSError errorWithDomain:@"com.luisobo.nocilla" code:123 userInfo:@{NSLocalizedDescriptionKey:@"Failing, failing... 1, 2, 3...", 190 | @"NSErrorFailingURLKey":[NSURL URLWithString:@"https://example.com/say-hello"], 191 | @"NSErrorFailingURLStringKey":@"https://example.com/say-hello" 192 | }]]; 193 | 194 | [[operation.error should] equal:[NSError errorWithDomain:@"com.luisobo.nocilla" code:123 userInfo:@{NSLocalizedDescriptionKey:@"Failing, failing... 1, 2, 3...", 195 | @"NSErrorFailingURLKey":[NSURL URLWithString:@"https://example.com/say-hello"], 196 | @"NSErrorFailingURLStringKey":@"https://example.com/say-hello" 197 | }]]; 198 | }); 199 | 200 | it(@"stubs the request with data", ^{ 201 | NSData *requestData = [@"data123" dataUsingEncoding:NSUTF8StringEncoding]; 202 | stubRequest(@"POST", @"https://example.com/say-hello"). 203 | withHeader(@"Content-Type", @"text/plain"). 204 | withHeader(@"X-MY-AWESOME-HEADER", @"sisisi"). 205 | withBody(requestData). 206 | andReturn(200). 207 | withHeader(@"Content-Type", @"text/plain"). 208 | withBody([@"eeeeooo" dataUsingEncoding:NSUTF8StringEncoding]); 209 | 210 | NSURL *url = [NSURL URLWithString:@"https://example.com/say-hello"]; 211 | NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; 212 | [request setHTTPMethod:@"POST"]; 213 | [request setValue:@"text/plain" forHTTPHeaderField:@"Content-Type"]; 214 | [request setValue:@"sisisi" forHTTPHeaderField:@"X-MY-AWESOME-HEADER"]; 215 | [request setHTTPBody:requestData]; 216 | AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request]; 217 | [operation start]; 218 | 219 | [operation waitUntilFinished]; 220 | 221 | [operation.error shouldBeNil]; 222 | [[operation.responseString should] equal:@"eeeeooo"]; 223 | [[theValue(operation.response.statusCode) should] equal:theValue(200)]; 224 | [[[operation.response.allHeaderFields objectForKey:@"Content-Type"] should] equal:@"text/plain"]; 225 | 226 | }); 227 | 228 | it(@"stubs the request with multipart request", ^{ 229 | __block BOOL done = NO; 230 | __block AFHTTPRequestOperation *capturedOperation; 231 | __block id capturedResponseObject; 232 | 233 | NSData *requestData = [@"data123" dataUsingEncoding:NSUTF8StringEncoding]; 234 | stubRequest(@"POST", @"https://example.com/say-hello"). 235 | andReturn(200). 236 | withHeader(@"Content-Type", @"application/json"). 237 | withBody(@"{\"foo\":\"bar\"}"); 238 | 239 | NSURL *url = [NSURL URLWithString:@"https://example.com"]; 240 | AFHTTPRequestOperationManager * client = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:url]; 241 | NSInputStream * stream = [[NSInputStream alloc] initWithData:requestData]; 242 | 243 | [client POST:@"say-hello" parameters:nil constructingBodyWithBlock:^(id formData) { 244 | [formData appendPartWithInputStream:stream name:@"name" fileName:@"filename" length:10 mimeType:@"application/octet-stream"]; 245 | } success:^(AFHTTPRequestOperation *operation, id responseObject) { 246 | capturedOperation = operation; 247 | capturedResponseObject = responseObject; 248 | done = YES; 249 | } failure:^(AFHTTPRequestOperation *operation, NSError *error) { 250 | done = YES; 251 | }]; 252 | 253 | [[expectFutureValue(theValue(done)) shouldEventually] beYes]; 254 | 255 | [capturedOperation.error shouldBeNil]; 256 | [[capturedOperation.responseString should] equal:@"{\"foo\":\"bar\"}"]; 257 | [[theValue(capturedOperation.response.statusCode) should] equal:theValue(200)]; 258 | [[[capturedOperation.response.allHeaderFields objectForKey:@"Content-Type"] should] equal:@"application/json"]; 259 | [[capturedResponseObject should] equal:@{@"foo": @"bar"}]; 260 | }); 261 | 262 | it(@"should properly handle redirects", ^{ 263 | stubRequest(@"GET", @"https://example.com/hello"). 264 | andReturn(301). 265 | withHeader(@"Location", @"https://example.com/hola"); 266 | 267 | stubRequest(@"GET", @"https://example.com/hola"). 268 | andReturn(200). 269 | withHeader(@"Content-Type", @"text/plain"). 270 | withBody(@"hola"); 271 | 272 | NSURL *url = [NSURL URLWithString:@"https://example.com/hello"]; 273 | NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; 274 | AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request]; 275 | [operation start]; 276 | 277 | [operation waitUntilFinished]; 278 | 279 | [operation.error shouldBeNil]; 280 | [[operation.responseString should] equal:@"hola"]; 281 | [[theValue(operation.response.statusCode) should] equal:theValue(200)]; 282 | [[[operation.response.allHeaderFields objectForKey:@"Content-Type"] should] equal:@"text/plain"]; 283 | }); 284 | 285 | it(@"should properly sets cookies on a non-redirected request", ^{ 286 | stubRequest(@"GET", @"https://example.com/hello"). 287 | andReturn(200). 288 | withBody(@"hola"). 289 | withHeader(@"Content-Type", @"text/plain"). 290 | withHeader(@"Set-Cookie", @"giveme=cookies;Path=/;"); 291 | 292 | NSURL *url = [NSURL URLWithString:@"https://example.com/hello"]; 293 | NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; 294 | AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request]; 295 | [operation start]; 296 | 297 | [operation waitUntilFinished]; 298 | 299 | NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage]; 300 | NSArray *cookies = [cookieStorage cookiesForURL:[NSURL URLWithString:@"https://example.com/"]]; 301 | [[cookies should] haveCountOf:1]; 302 | NSHTTPCookie *cookie = cookies[0]; 303 | [[[cookie name] should] equal:@"giveme"]; 304 | [[[cookie value] should] equal:@"cookies"]; 305 | }); 306 | 307 | it(@"should properly sets cookies on redirect", ^{ 308 | stubRequest(@"GET", @"https://example.com/hello"). 309 | andReturn(301). 310 | withHeaders(@{@"Location": @"https://example.com/hola", 311 | @"Set-Cookie": @"giveme=cookies;Path=/;"}); 312 | 313 | stubRequest(@"GET", @"https://example.com/hola"). 314 | withHeader(@"Cookie", @"giveme=cookies"). 315 | andReturn(200). 316 | withHeader(@"Content-Type", @"text/plain"). 317 | withBody(@"hola"); 318 | 319 | NSURL *url = [NSURL URLWithString:@"https://example.com/hello"]; 320 | NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; 321 | AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request]; 322 | [operation start]; 323 | 324 | [operation waitUntilFinished]; 325 | 326 | NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage]; 327 | NSArray *cookies = [cookieStorage cookiesForURL:[NSURL URLWithString:@"https://example.com/"]]; 328 | [[cookies should] haveCountOf:1]; 329 | NSHTTPCookie *cookie = cookies[0]; 330 | [[[cookie name] should] equal:@"giveme"]; 331 | [[[cookie value] should] equal:@"cookies"]; 332 | 333 | }); 334 | }); 335 | SPEC_END -------------------------------------------------------------------------------- /NocillaTests/Hooks/NSURLRequest/LSHTTPStubURLProtocolSpec.m: -------------------------------------------------------------------------------- 1 | #import "Kiwi.h" 2 | #import "LSHTTPStubURLProtocol.h" 3 | #import "LSStubRequest.h" 4 | #import "LSStubResponse.h" 5 | #import "LSNocilla.h" 6 | 7 | @interface NSHTTPURLResponse(UndocumentedInitializer) 8 | - (id)initWithURL:(NSURL*)URL statusCode:(NSInteger)statusCode headerFields:(NSDictionary*)headerFields requestTime:(double)requestTime; 9 | @end 10 | 11 | @interface LSTestingNSURLProtocolClient : NSObject 12 | @property (nonatomic, strong) NSURLResponse *response; 13 | @property (nonatomic, strong) NSData *body; 14 | @end 15 | 16 | @implementation LSTestingNSURLProtocolClient 17 | - (void)URLProtocol:(NSURLProtocol *)protocol wasRedirectedToRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse { 18 | 19 | } 20 | - (void)URLProtocol:(NSURLProtocol *)protocol cachedResponseIsValid:(NSCachedURLResponse *)cachedResponse { 21 | 22 | } 23 | - (void)URLProtocol:(NSURLProtocol *)protocol didReceiveResponse:(NSURLResponse *)response cacheStoragePolicy:(NSURLCacheStoragePolicy)policy { 24 | self.response = response; 25 | } 26 | - (void)URLProtocol:(NSURLProtocol *)protocol didLoadData:(NSData *)data { 27 | self.body = data; 28 | 29 | } 30 | - (void)URLProtocolDidFinishLoading:(NSURLProtocol *)protocol { 31 | 32 | } 33 | - (void)URLProtocol:(NSURLProtocol *)protocol didFailWithError:(NSError *)error { 34 | 35 | } 36 | - (void)URLProtocol:(NSURLProtocol *)protocol didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { 37 | 38 | } 39 | - (void)URLProtocol:(NSURLProtocol *)protocol didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { 40 | 41 | } 42 | 43 | 44 | @end 45 | SPEC_BEGIN(LSHTTPStubURLProtocolSpec) 46 | 47 | describe(@"+canInitWithRequest", ^{ 48 | context(@"when it is a HTTP request", ^{ 49 | it(@"should return YES", ^{ 50 | NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:@"http://api.example.com/dogs.json"]]; 51 | 52 | BOOL result = [LSHTTPStubURLProtocol canInitWithRequest:request]; 53 | 54 | [[theValue(result) should] beYes]; 55 | }); 56 | }); 57 | context(@"when it is a HTTP request", ^{ 58 | it(@"should return YES", ^{ 59 | NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:@"https://api.example.com/cats.xml"]]; 60 | 61 | BOOL result = [LSHTTPStubURLProtocol canInitWithRequest:request]; 62 | 63 | [[theValue(result) should] beYes]; 64 | }); 65 | }); 66 | 67 | context(@"when it is an FTP request", ^{ 68 | it(@"should return NO", ^{ 69 | NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:@"ftp://ftp.example.com/"]]; 70 | 71 | BOOL result = [LSHTTPStubURLProtocol canInitWithRequest:request]; 72 | 73 | [[theValue(result) should] beNo]; 74 | }); 75 | }); 76 | }); 77 | 78 | describe(@"#startLoading", ^{ 79 | context(@"when the protocol receives a request", ^{ 80 | __block NSString *stringUrl = nil; 81 | __block NSString *bodyString = nil; 82 | __block LSHTTPStubURLProtocol *protocol = nil; 83 | __block LSTestingNSURLProtocolClient *client = nil; 84 | beforeEach(^{ 85 | stringUrl = @"http://api.example.com/dogs.xml"; 86 | NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:stringUrl]]; 87 | 88 | client = [[LSTestingNSURLProtocolClient alloc] init]; 89 | 90 | protocol = [[LSHTTPStubURLProtocol alloc] initWithRequest:request cachedResponse:nil client:client]; 91 | }); 92 | context(@"that matches an stubbed request", ^{ 93 | context(@"and the response should succeed", ^{ 94 | beforeEach(^{ 95 | LSStubRequest *stubRequest = [[LSStubRequest alloc] initWithMethod:@"GET" url:stringUrl]; 96 | LSStubResponse *stubResponse = [[LSStubResponse alloc] initWithStatusCode:403]; 97 | [stubResponse setHeader:@"Content-Type" value:@"application/xml"]; 98 | bodyString = @"Error" ; 99 | stubResponse.body = [bodyString dataUsingEncoding:NSUTF8StringEncoding]; 100 | stubRequest.response = stubResponse; 101 | 102 | [[LSNocilla sharedInstance] stub:@selector(stubbedRequests) andReturn:@[stubRequest]]; 103 | }); 104 | 105 | it(@"should pass to the client the correct response", ^{ 106 | [protocol startLoading]; 107 | 108 | [[client.response should] beKindOfClass:[NSHTTPURLResponse class]]; 109 | NSHTTPURLResponse *response = (NSHTTPURLResponse *)client.response; 110 | [[response.URL should] equal:[NSURL URLWithString:stringUrl]]; 111 | [[theValue(response.statusCode) should] equal:theValue(403)]; 112 | [[response.allHeaderFields should] equal:@{ @"Content-Type": @"application/xml"}]; 113 | }); 114 | it(@"should pass the body to the client", ^{ 115 | 116 | [[client should] receive:@selector(URLProtocol:didLoadData:) withArguments:protocol, [bodyString dataUsingEncoding:NSUTF8StringEncoding]]; 117 | 118 | [protocol startLoading]; 119 | 120 | }); 121 | 122 | it(@"should notify the client that it finished loading", ^{ 123 | [[client should] receive:@selector(URLProtocolDidFinishLoading:)]; 124 | 125 | [protocol startLoading]; 126 | }); 127 | }); 128 | 129 | context(@"and the response should fail", ^{ 130 | __block NSError *error; 131 | beforeEach(^{ 132 | LSStubRequest *stubRequest = [[LSStubRequest alloc] initWithMethod:@"GET" url:stringUrl]; 133 | error = [NSError mock]; 134 | LSStubResponse *stubResponse = [[LSStubResponse alloc] initWithError:error]; 135 | stubRequest.response = stubResponse; 136 | 137 | [[LSNocilla sharedInstance] stub:@selector(stubbedRequests) andReturn:@[stubRequest]]; 138 | }); 139 | 140 | it(@"should notify the client that it failed", ^{ 141 | [[client should] receive:@selector(URLProtocol:didFailWithError:) withArguments:protocol, error]; 142 | 143 | [protocol startLoading]; 144 | }); 145 | }); 146 | }); 147 | context(@"that doesn't match any stubbed request", ^{ 148 | it(@"should raise an exception with a meaningful message", ^{ 149 | NSString *expectedMessage = @"An unexpected HTTP request was fired.\n\nUse this snippet to stub the request:\nstubRequest(@\"GET\", @\"http://api.example.com/dogs.xml\");\n"; 150 | [[theBlock(^{ 151 | [protocol startLoading]; 152 | }) should] raiseWithName:@"NocillaUnexpectedRequest" reason:expectedMessage]; 153 | }); 154 | }); 155 | }); 156 | }); 157 | 158 | SPEC_END -------------------------------------------------------------------------------- /NocillaTests/Hooks/NSURLRequest/MKNetworkKitStubbingSpec.m: -------------------------------------------------------------------------------- 1 | #import "Kiwi.h" 2 | #import "MKNetworkKit.h" 3 | #import "Nocilla.h" 4 | #import "HTTPServer.h" 5 | #import "LSTestingConnection.h" 6 | 7 | SPEC_BEGIN(MKNetworkKitStubbingSpec) 8 | 9 | beforeAll(^{ 10 | [[LSNocilla sharedInstance] start]; 11 | }); 12 | afterAll(^{ 13 | [[LSNocilla sharedInstance] stop]; 14 | }); 15 | afterEach(^{ 16 | [[LSNocilla sharedInstance] clearStubs]; 17 | }); 18 | 19 | context(@"MKNetworkKit", ^{ 20 | it(@"should stub an asynchronous request", ^{ 21 | stubRequest(@"POST", @"http://example.com/say-hello"). 22 | withHeaders(@{ @"X-MY-AWESOME-HEADER": @"sisisi", @"Content-Type": @"application/json; charset=utf-8" }). 23 | withBody(@"{\"text\":\"hola\"}"). 24 | andReturn(200). 25 | withHeader(@"Content-Type", @"text/plain"). 26 | withBody(@"{\"text\":\"adios\"}"); 27 | 28 | MKNetworkOperation *operation = [[MKNetworkOperation alloc] 29 | initWithURLString:@"http://example.com/say-hello" 30 | params:[@{ @"text" : @"hola" } mutableCopy] 31 | httpMethod:@"POST"]; 32 | [operation addHeaders: @{ 33 | @"Content-Type" : @"text/plain", 34 | @"X-MY-AWESOME-HEADER" : @"sisisi" }]; 35 | 36 | [operation setPostDataEncoding:MKNKPostDataEncodingTypeJSON]; 37 | [operation start]; 38 | 39 | 40 | 41 | 42 | [[expectFutureValue(operation.responseString) shouldEventually] equal:@"{\"text\":\"adios\"}"]; 43 | 44 | [operation.error shouldBeNil]; 45 | [[theValue(operation.readonlyResponse.statusCode) should] equal:theValue(200)]; 46 | [[[operation.readonlyResponse.allHeaderFields objectForKey:@"Content-Type"] should] equal:@"text/plain"]; 47 | }); 48 | 49 | it(@"should have the same result as a real HTTP request", ^{ 50 | NSURL *url = [NSURL URLWithString:@"http://localhost:12345/say-hello"]; 51 | 52 | [[LSNocilla sharedInstance] stop]; 53 | 54 | HTTPServer *server = [[HTTPServer alloc] init]; 55 | [server setPort:12345]; 56 | NSError *error; 57 | [server setConnectionClass:[LSTestingConnection class]]; 58 | [server start:&error]; 59 | [error shouldBeNil]; 60 | 61 | 62 | MKNetworkOperation *realOperation = [[MKNetworkOperation alloc] 63 | initWithURLString:url.absoluteString 64 | params:[@{ @"this is" : @"the body" } mutableCopy] 65 | httpMethod:@"POST"]; 66 | [realOperation addHeaders: @{ 67 | @"Content-Type" : @"application/json", 68 | @"X-MY-AWESOME-HEADER" : @"sisisi" }]; 69 | 70 | [realOperation setPostDataEncoding:MKNKPostDataEncodingTypeJSON]; 71 | [realOperation start]; 72 | 73 | 74 | [[expectFutureValue(theValue(realOperation.readonlyResponse.statusCode)) shouldEventually] equal:theValue(200)]; 75 | [realOperation.error shouldBeNil]; 76 | [[realOperation.responseString should] equal:@"hola"]; 77 | 78 | 79 | NSHTTPURLResponse *realResponse = realOperation.readonlyResponse; 80 | NSString *realBody = realOperation.responseString; 81 | 82 | [server stop]; 83 | 84 | [[LSNocilla sharedInstance] start]; 85 | 86 | stubRequest(@"POST", @"http://localhost:12345/say-hello"). 87 | withHeaders(@{ @"Content-Type": @"application/json; charset=utf-8", @"X-MY-AWESOME-HEADER": @"sisisi" }). 88 | withBody(@"{\"this is\":\"the body\"}"). 89 | andReturn(200). 90 | withHeaders([realResponse allHeaderFields]). 91 | withBody(@"hola"); 92 | 93 | 94 | 95 | MKNetworkOperation *stubbedOperation = [[MKNetworkOperation alloc] 96 | initWithURLString:url.absoluteString 97 | params:[@{ @"this is" : @"the body" } mutableCopy] 98 | httpMethod:@"POST"]; 99 | [stubbedOperation addHeaders: @{ 100 | @"Content-Type" : @"application/json", 101 | @"X-MY-AWESOME-HEADER" : @"sisisi" }]; 102 | 103 | [stubbedOperation setPostDataEncoding:MKNKPostDataEncodingTypeJSON]; 104 | [stubbedOperation start]; 105 | 106 | [[expectFutureValue(theValue(stubbedOperation.readonlyResponse.statusCode)) shouldEventually] equal:theValue(200)]; 107 | 108 | [[stubbedOperation.responseString should] equal:@"hola"]; 109 | [stubbedOperation.error shouldBeNil]; 110 | 111 | 112 | 113 | NSHTTPURLResponse *stubbedResponse = stubbedOperation.readonlyResponse; 114 | NSString *stubbedBody = stubbedOperation.responseString; 115 | 116 | 117 | NSDictionary *realHeaders = [realResponse allHeaderFields]; 118 | NSDictionary *stubbedHeaders = [stubbedResponse allHeaderFields]; 119 | [[theValue(realResponse.statusCode) should] equal:theValue(stubbedResponse.statusCode)]; 120 | [[realHeaders should] equal:stubbedHeaders]; 121 | [[realBody should] equal:stubbedBody]; 122 | }); 123 | 124 | }); 125 | SPEC_END -------------------------------------------------------------------------------- /NocillaTests/Hooks/NSURLRequest/NSURLRequest+LSHTTPRequestSpec.m: -------------------------------------------------------------------------------- 1 | #import "Kiwi.h" 2 | #import "NSURLRequest+LSHTTPRequest.h" 3 | 4 | SPEC_BEGIN(NSURLRequest_LSHTTPRequestSpec) 5 | 6 | describe(@"-body", ^{ 7 | __block NSURLRequest *request; 8 | 9 | context(@"when the request provides a body stream", ^{ 10 | __block NSString *stringUrl = @"http://api.example.com/dogs.xml"; 11 | __block NSData *requestBody = [@"arg1=foo&arg2=bar" dataUsingEncoding:NSUTF8StringEncoding]; 12 | 13 | beforeEach(^{ 14 | NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:stringUrl]]; 15 | mutableRequest.HTTPBodyStream = [NSInputStream inputStreamWithData:requestBody]; 16 | request = mutableRequest; 17 | }); 18 | 19 | it(@"uses the entire stream as the body", ^{ 20 | [[[request body] should] equal:requestBody]; 21 | }); 22 | }); 23 | }); 24 | 25 | SPEC_END 26 | -------------------------------------------------------------------------------- /NocillaTests/Hooks/NSURLRequest/NSURLRequestHookSpec.m: -------------------------------------------------------------------------------- 1 | #import "Kiwi.h" 2 | #import "LSNSURLHook.h" 3 | #import "LSHTTPStubURLProtocol.h" 4 | 5 | SPEC_BEGIN(LSNSURLHookSpec) 6 | describe(@"#load", ^{ 7 | it(@"should register LSHTTPStubURLProtocol as a NSURLProtocol", ^{ 8 | LSNSURLHook *hook = [[LSNSURLHook alloc] init]; 9 | [[NSURLProtocol should] receive:@selector(registerClass:) withArguments:[LSHTTPStubURLProtocol class]]; 10 | 11 | [hook load]; 12 | }); 13 | }); 14 | 15 | describe(@"#unload", ^{ 16 | it(@"should unregister LSHTTPStubURLProtocol as a NSURLProtocol", ^{ 17 | LSNSURLHook *hook = [[LSNSURLHook alloc] init]; 18 | 19 | [[NSURLProtocol should] receive:@selector(unregisterClass:) withArguments:[LSHTTPStubURLProtocol class]]; 20 | 21 | [hook unload]; 22 | }); 23 | }); 24 | SPEC_END -------------------------------------------------------------------------------- /NocillaTests/Hooks/NSURLSession/NSURLSessionStubbingSpec.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSURLSessionStubbingSpec.m 3 | // Nocilla 4 | // 5 | // Created by Luis Solano Bonet on 08/01/14. 6 | // Copyright 2014 Luis Solano Bonet. All rights reserved. 7 | // 8 | 9 | #import "Kiwi.h" 10 | #import "Nocilla.h" 11 | #import "LSHTTPStubURLProtocol.h" 12 | 13 | SPEC_BEGIN(NSURLSessionStubbingSpec) 14 | if (NSClassFromString(@"NSURLSession") == nil) return; 15 | 16 | beforeEach(^{ 17 | [[LSNocilla sharedInstance] start]; 18 | }); 19 | afterEach(^{ 20 | [[LSNocilla sharedInstance] stop]; 21 | [[LSNocilla sharedInstance] clearStubs]; 22 | }); 23 | 24 | it(@"stubs NSURLSessionDataTask", ^{ 25 | stubRequest(@"GET", @"http://example.com") 26 | .andReturn(200) 27 | .withBody(@"this is a counter example."); 28 | __block BOOL done = NO; 29 | __block NSData *capturedData; 30 | NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; 31 | 32 | NSURL *url = [NSURL URLWithString:@"http://example.com"]; 33 | NSURLSession *urlsession = [NSURLSession sessionWithConfiguration:configuration]; 34 | NSURLSessionDataTask *datatask = [urlsession dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { 35 | done = YES; 36 | capturedData = data; 37 | }]; 38 | [datatask resume]; 39 | 40 | 41 | [[expectFutureValue(theValue(done)) shouldEventually] beYes]; 42 | [[[[NSString alloc] initWithData:capturedData encoding:NSUTF8StringEncoding] should] equal:@"this is a counter example."]; 43 | }); 44 | 45 | SPEC_END 46 | -------------------------------------------------------------------------------- /NocillaTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.luissolano.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /NocillaTests/LSNocillaSpec.m: -------------------------------------------------------------------------------- 1 | #import "Kiwi.h" 2 | #import "LSNocilla.h" 3 | #import "LSStubRequest.h" 4 | #import "LSStubResponse.h" 5 | 6 | SPEC_BEGIN(LSNocillaSpec) 7 | 8 | describe(@"-responseForRequest:", ^{ 9 | context(@"when the specified request matches a previously stubbed request", ^{ 10 | it(@"returns the associated response", ^{ 11 | LSStubRequest *stubbedRequest = [[LSStubRequest alloc] initWithMethod:@"GET" url:@"http://www.google.com"]; 12 | LSStubResponse *stubbedResponse = [[LSStubResponse alloc] initWithStatusCode:403]; 13 | stubbedRequest.response = stubbedResponse; 14 | [[LSNocilla sharedInstance] addStubbedRequest:stubbedRequest]; 15 | 16 | NSObject *actualRequest = [KWMock nullMockForProtocol:@protocol(LSHTTPRequest)]; 17 | [actualRequest stub:@selector(url) andReturn:[NSURL URLWithString:@"http://www.google.com"]]; 18 | [actualRequest stub:@selector(method) andReturn:@"GET"]; 19 | 20 | LSStubResponse *result = [[LSNocilla sharedInstance] responseForRequest:actualRequest]; 21 | 22 | [[result should] equal:stubbedResponse]; 23 | 24 | [[LSNocilla sharedInstance] clearStubs]; 25 | }); 26 | 27 | describe(@"when a stubbed request is replaced with a new stub", ^{ 28 | it(@"returns the response for the newer stub", ^{ 29 | LSStubRequest *firstStub = [[LSStubRequest alloc] initWithMethod:@"GET" url:@"http://www.google.com"]; 30 | LSStubResponse *firstResponse = [[LSStubResponse alloc] initWithStatusCode:403]; 31 | firstStub.response = firstResponse; 32 | [[LSNocilla sharedInstance] addStubbedRequest:firstStub]; 33 | 34 | LSStubRequest *secondStub = [[LSStubRequest alloc] initWithMethod:@"GET" url:@"http://www.google.com"]; 35 | LSStubResponse *secondResponse = [[LSStubResponse alloc] initWithStatusCode:200]; 36 | secondStub.response = secondResponse; 37 | [[LSNocilla sharedInstance] addStubbedRequest:secondStub]; 38 | 39 | NSObject *actualRequest = [KWMock nullMockForProtocol:@protocol(LSHTTPRequest)]; 40 | [actualRequest stub:@selector(url) andReturn:[NSURL URLWithString:@"http://www.google.com"]]; 41 | [actualRequest stub:@selector(method) andReturn:@"GET"]; 42 | 43 | LSStubResponse *result = [[LSNocilla sharedInstance] responseForRequest:actualRequest]; 44 | 45 | [[result shouldNot] equal:firstResponse]; 46 | [[result should] equal:secondResponse]; 47 | 48 | [[LSNocilla sharedInstance] clearStubs]; 49 | }); 50 | }); 51 | }); 52 | 53 | context(@"when the specified request does not match any stubbed request", ^{ 54 | it(@"raises an exception with a descriptive error message ", ^{ 55 | NSObject *actualRequest = [KWMock nullMockForProtocol:@protocol(LSHTTPRequest)]; 56 | [actualRequest stub:@selector(url) andReturn:[NSURL URLWithString:@"http://www.google.com"]]; 57 | [actualRequest stub:@selector(method) andReturn:@"GET"]; 58 | 59 | 60 | NSString *expectedMessage = @"An unexpected HTTP request was fired.\n\nUse this snippet to stub the request:\nstubRequest(@\"GET\", @\"http://www.google.com\");\n"; 61 | [[theBlock(^{ 62 | [[LSNocilla sharedInstance] responseForRequest:actualRequest]; 63 | }) should] raiseWithName:@"NocillaUnexpectedRequest" reason:expectedMessage]; 64 | 65 | [[LSNocilla sharedInstance] clearStubs]; 66 | }); 67 | }); 68 | }); 69 | 70 | SPEC_END -------------------------------------------------------------------------------- /NocillaTests/LSStubResponseSpec.m: -------------------------------------------------------------------------------- 1 | #import "Kiwi.h" 2 | #import "LSStubResponse.h" 3 | 4 | SPEC_BEGIN(LSStubResponseSpec) 5 | 6 | describe(@"#initWithRawResponse:", ^{ 7 | context(@"when the input data is a complete raw response", ^{ 8 | it(@"should have the correct statusCode, headers and body", ^{ 9 | NSData *rawData = [@"HTTP/1.1 201 created\nContent-Type: application/json; charset=utf-8\nConnection: keep-alive\n\n{\"success\":true}" dataUsingEncoding:NSUTF8StringEncoding]; 10 | LSStubResponse *stubResponse = [[LSStubResponse alloc] initWithRawResponse:rawData]; 11 | 12 | [[theValue(stubResponse.statusCode) should] equal:theValue(201)]; 13 | [[stubResponse.headers should] equal:[NSDictionary dictionaryWithObjectsAndKeys:@"application/json; charset=utf-8", @"Content-Type", 14 | @"keep-alive", @"Connection", nil]]; 15 | [[stubResponse.body should] equal:[@"{\"success\":true}" dataUsingEncoding:NSUTF8StringEncoding]]; 16 | }); 17 | }); 18 | 19 | context(@"when the input data is a just the raw body", ^{ 20 | it(@"should have the correct body with default status code and headers", ^{ 21 | NSData *rawData = [@"{\"success\":true}" dataUsingEncoding:NSUTF8StringEncoding]; 22 | LSStubResponse *stubResponse = [[LSStubResponse alloc] initWithRawResponse:rawData]; 23 | 24 | [[theValue(stubResponse.statusCode) should] equal:theValue(200)]; 25 | [[stubResponse.headers should] equal:[NSDictionary dictionary]]; 26 | [[stubResponse.body should] equal:[@"{\"success\":true}" dataUsingEncoding:NSUTF8StringEncoding]]; 27 | }); 28 | }); 29 | }); 30 | 31 | SPEC_END -------------------------------------------------------------------------------- /NocillaTests/LSTestRequest.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import "LSHTTPRequest.h" 4 | 5 | @interface LSTestRequest : NSObject 6 | 7 | @property (nonatomic, strong) NSURL *url; 8 | @property (nonatomic, strong) NSString *method; 9 | @property (nonatomic, readonly) NSDictionary *headers; 10 | @property (nonatomic, strong) NSData *body; 11 | 12 | - (instancetype)initWithMethod:(NSString *)method url:(NSString *)url; 13 | - (void)setHeader:(NSString *)header value:(NSString *)value; 14 | @end 15 | -------------------------------------------------------------------------------- /NocillaTests/LSTestRequest.m: -------------------------------------------------------------------------------- 1 | #import "LSTestRequest.h" 2 | 3 | @interface LSTestRequest () 4 | @property (nonatomic, strong) NSMutableDictionary *mutableHeaders; 5 | @end 6 | 7 | @implementation LSTestRequest 8 | 9 | - (instancetype)initWithMethod:(NSString *)method url:(NSString *)url { 10 | self = [super init]; 11 | if (self) { 12 | _method = method; 13 | _url = [NSURL URLWithString:url]; 14 | _mutableHeaders = [NSMutableDictionary dictionary]; 15 | } 16 | return self; 17 | } 18 | 19 | - (void)setHeader:(NSString *)header value:(NSString *)value { 20 | self.mutableHeaders[header] = value; 21 | } 22 | 23 | - (NSDictionary *)headers { 24 | return [self.mutableHeaders copy]; 25 | } 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /NocillaTests/Matchers/LSDataMatcherSpec.m: -------------------------------------------------------------------------------- 1 | #import "Kiwi.h" 2 | #import "LSDataMatcher.h" 3 | 4 | SPEC_BEGIN(LSDataMatcherSpec) 5 | 6 | __block LSDataMatcher *matcher = nil; 7 | __block NSData *data = nil; 8 | 9 | beforeEach(^{ 10 | const char bytes[] = { 0xF1, 0x00, 0xFF }; 11 | data = [NSData dataWithBytes:bytes length:sizeof(bytes)]; 12 | matcher = [[LSDataMatcher alloc] initWithData:data]; 13 | }); 14 | 15 | context(@"when both data are equal", ^{ 16 | it(@"matches", ^{ 17 | [[theValue([matcher matchesData:[data copy]]) should] beYes]; 18 | }); 19 | }); 20 | 21 | context(@"when both data are different", ^{ 22 | it(@"does not match", ^{ 23 | const char other_bytes[] = { 0xA1, 0x00, 0xAF }; 24 | NSData *other_data = [NSData dataWithBytes:other_bytes length:sizeof(other_bytes)]; 25 | 26 | [[theValue([matcher matchesData:other_data]) should] beNo]; 27 | }); 28 | }); 29 | 30 | describe(@"isEqual:", ^{ 31 | it(@"is equal to another data matcher with the same data", ^{ 32 | LSDataMatcher *matcherA = [[LSDataMatcher alloc] initWithData:[@"same" dataUsingEncoding:NSUTF8StringEncoding]]; 33 | LSDataMatcher *matcherB = [[LSDataMatcher alloc] initWithData:[@"same" dataUsingEncoding:NSUTF8StringEncoding]]; 34 | 35 | [[matcherA should] equal:matcherB]; 36 | }); 37 | 38 | it(@"is not equal to another data matcher with a different data", ^{ 39 | LSDataMatcher *matcherA = [[LSDataMatcher alloc] initWithData:[@"omg" dataUsingEncoding:NSUTF8StringEncoding]]; 40 | LSDataMatcher *matcherB = [[LSDataMatcher alloc] initWithData:[@"different" dataUsingEncoding:NSUTF8StringEncoding]]; 41 | 42 | [[matcherA shouldNot] equal:matcherB]; 43 | }); 44 | }); 45 | 46 | SPEC_END 47 | -------------------------------------------------------------------------------- /NocillaTests/Matchers/LSRegexMatcherSpec.m: -------------------------------------------------------------------------------- 1 | #import "Kiwi.h" 2 | #import "LSRegexMatcher.h" 3 | #import "NSString+Nocilla.h" 4 | 5 | SPEC_BEGIN(LSRegexMatcherSpec) 6 | 7 | __block LSRegexMatcher *matcher = nil; 8 | beforeEach(^{ 9 | matcher = [[LSRegexMatcher alloc] initWithRegex:@"Fo+".regex]; 10 | }); 11 | 12 | context(@"when the string matches the regex", ^{ 13 | it(@"matches", ^{ 14 | [[theValue([matcher matches:@"Fo"]) should] beYes]; 15 | [[theValue([matcher matches:@"Foo"]) should] beYes]; 16 | [[theValue([matcher matches:@"Foooooo"]) should] beYes]; 17 | }); 18 | }); 19 | 20 | context(@"when string does not match", ^{ 21 | it(@"does not match", ^{ 22 | [[theValue([matcher matches:@"fo"]) should] beNo]; 23 | [[theValue([matcher matches:@"F"]) should] beNo]; 24 | [[theValue([matcher matches:@"bar"]) should] beNo]; 25 | }); 26 | }); 27 | 28 | describe(@"isEqual:", ^{ 29 | it(@"is equal to another regex matcher with the same regex", ^{ 30 | LSRegexMatcher *matcherA = [[LSRegexMatcher alloc] initWithRegex:@"([same]+)".regex]; 31 | LSRegexMatcher *matcherB = [[LSRegexMatcher alloc] initWithRegex:@"([same]+)".regex]; 32 | 33 | [[matcherA should] equal:matcherB]; 34 | }); 35 | 36 | it(@"is not equal to another regex matcher with a different regex", ^{ 37 | LSRegexMatcher *matcherA = [[LSRegexMatcher alloc] initWithRegex:@"([omg]+)".regex]; 38 | LSRegexMatcher *matcherB = [[LSRegexMatcher alloc] initWithRegex:@"([different]+)".regex]; 39 | 40 | [[matcherA shouldNot] equal:matcherB]; 41 | }); 42 | }); 43 | 44 | SPEC_END -------------------------------------------------------------------------------- /NocillaTests/Matchers/LSStringMatcherSpec.m: -------------------------------------------------------------------------------- 1 | #import "Kiwi.h" 2 | #import "LSStringMatcher.h" 3 | 4 | SPEC_BEGIN(LSStringMatcherSpec) 5 | 6 | __block LSStringMatcher *matcher = nil; 7 | beforeEach(^{ 8 | matcher = [[LSStringMatcher alloc] initWithString:@"foo"]; 9 | }); 10 | 11 | context(@"when both strings are equal", ^{ 12 | it(@"matches", ^{ 13 | [[theValue([matcher matches:@"foo"]) should] beYes]; 14 | }); 15 | }); 16 | 17 | context(@"when both strings are different", ^{ 18 | it(@"does not match", ^{ 19 | [[theValue([matcher matches:@"bar"]) should] beNo]; 20 | }); 21 | }); 22 | 23 | describe(@"isEqual:", ^{ 24 | it(@"is equal to another string matcher with the same string", ^{ 25 | LSStringMatcher *matcherA = [[LSStringMatcher alloc] initWithString:@"same"]; 26 | LSStringMatcher *matcherB = [[LSStringMatcher alloc] initWithString:@"same"]; 27 | 28 | [[matcherA should] equal:matcherB]; 29 | }); 30 | 31 | it(@"is not equal to another string matcher with a different string", ^{ 32 | LSStringMatcher *matcherA = [[LSStringMatcher alloc] initWithString:@"omg"]; 33 | LSStringMatcher *matcherB = [[LSStringMatcher alloc] initWithString:@"different"]; 34 | 35 | [[matcherA shouldNot] equal:matcherB]; 36 | }); 37 | }); 38 | 39 | SPEC_END -------------------------------------------------------------------------------- /NocillaTests/NocillaTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /NocillaTests/Stubs/LSStubRequestSpec.m: -------------------------------------------------------------------------------- 1 | #import "Kiwi.h" 2 | #import "LSStubRequest.h" 3 | #import "LSTestRequest.h" 4 | #import "NSString+Nocilla.h" 5 | #import "LSRegexMatcher.h" 6 | #import "LSDataMatcher.h" 7 | #import "LSStringMatcher.h" 8 | 9 | SPEC_BEGIN(LSStubRequestSpec) 10 | 11 | describe(@"#matchesRequest:", ^{ 12 | context(@"when the method and the URL are equal", ^{ 13 | it(@"should match", ^{ 14 | LSStubRequest *stubRequest = [[LSStubRequest alloc] initWithMethod:@"PUT" url:@"https://api.example.com/cats/whiskas.json"]; 15 | 16 | LSTestRequest *other = [[LSTestRequest alloc] initWithMethod:@"PUT" url:@"https://api.example.com/cats/whiskas.json"]; 17 | 18 | [[theValue([stubRequest matchesRequest:other]) should] beYes]; 19 | }); 20 | }); 21 | context(@"when the method is different", ^{ 22 | it(@"should not match", ^{ 23 | LSStubRequest *stubRequest = [[LSStubRequest alloc] initWithMethod:@"PUT" url:@"https://api.example.com/cats/whiskas.json"]; 24 | 25 | LSTestRequest *other = [[LSTestRequest alloc] initWithMethod:@"GET" url:@"https://api.example.com/cats/whiskas.json"]; 26 | 27 | [[theValue([stubRequest matchesRequest:other]) should] beNo]; 28 | }); 29 | }); 30 | context(@"when the URL is different", ^{ 31 | it(@"should not match", ^{ 32 | LSStubRequest *stubRequest = [[LSStubRequest alloc] initWithMethod:@"PUT" url:@"https://api.example.com/cats/whiskas.json"]; 33 | 34 | LSTestRequest *other = [[LSTestRequest alloc] initWithMethod:@"PUT" url:@"https://api.example.com/dogs/barky.json"]; 35 | 36 | [[theValue([stubRequest matchesRequest:other]) should] beNo]; 37 | }); 38 | }); 39 | 40 | context(@"when we use a regex matcher for the URL", ^{ 41 | __block LSStubRequest *stubRequest = nil; 42 | __block LSTestRequest *other = nil; 43 | beforeEach(^{ 44 | stubRequest = [[LSStubRequest alloc] initWithMethod:@"PUT" urlMatcher:[[LSRegexMatcher alloc] initWithRegex:@"^http://foo.com".regex]]; 45 | }); 46 | context(@"the the actual URL matches that regex", ^{ 47 | it(@"matches", ^{ 48 | other = [[LSTestRequest alloc] initWithMethod:@"PUT" url:@"http://foo.com/something.json"]; 49 | 50 | [[theValue([stubRequest matchesRequest:other]) should] beYes]; 51 | }); 52 | }); 53 | 54 | context(@"the the actual URL matches that regex", ^{ 55 | it(@"matches", ^{ 56 | other = [[LSTestRequest alloc] initWithMethod:@"PUT" url:@"asdhttp://foo.com"]; 57 | 58 | [[theValue([stubRequest matchesRequest:other]) should] beNo]; 59 | }); 60 | }); 61 | }); 62 | 63 | context(@"when the stub request has a subset of header of the actual request", ^{ 64 | __block LSStubRequest *stubRequest = nil; 65 | __block LSTestRequest *actualRequest = nil; 66 | beforeEach(^{ 67 | stubRequest = [[LSStubRequest alloc] initWithMethod:@"PUT" url:@"https://api.example.com/cats/whiskas.json"]; 68 | 69 | actualRequest = [[LSTestRequest alloc] initWithMethod:@"PUT" url:@"https://api.example.com/cats/whiskas.json"]; 70 | 71 | [stubRequest setHeader:@"Content-Type" value:@"application/json"]; 72 | 73 | [actualRequest setHeader:@"Content-Type" value:@"application/json"]; 74 | [actualRequest setHeader:@"X-API-TOKEN" value:@"123abc"]; 75 | }); 76 | describe(@"the stubRequest request", ^{ 77 | it(@"should match the actualRequest request", ^{ 78 | [[theValue([stubRequest matchesRequest:actualRequest]) should] beYes]; 79 | }); 80 | }); 81 | 82 | }); 83 | 84 | context(@"when the stub request has a perset of headers of the actual request", ^{ 85 | __block LSStubRequest *stubRequest = nil; 86 | __block LSTestRequest *actualRequest = nil; 87 | beforeEach(^{ 88 | stubRequest = [[LSStubRequest alloc] initWithMethod:@"PUT" url:@"https://api.example.com/cats/whiskas.json"]; 89 | 90 | actualRequest = [[LSTestRequest alloc] initWithMethod:@"PUT" url:@"https://api.example.com/cats/whiskas.json"]; 91 | 92 | [stubRequest setHeader:@"Content-Type" value:@"application/json"]; 93 | [stubRequest setHeader:@"X-API-TOKEN" value:@"123abc"]; 94 | 95 | [actualRequest setHeader:@"Content-Type" value:@"application/json"]; 96 | }); 97 | describe(@"the actualRequest request", ^{ 98 | it(@"should not match the stubRequest request", ^{ 99 | [[theValue([stubRequest matchesRequest:actualRequest]) should] beNo]; 100 | }); 101 | }); 102 | }); 103 | 104 | context(@"when the both requests have the same headers", ^{ 105 | __block LSStubRequest *stubRequest = nil; 106 | __block LSTestRequest *actualRequest = nil; 107 | beforeEach(^{ 108 | stubRequest = [[LSStubRequest alloc] initWithMethod:@"PUT" url:@"https://api.example.com/cats/whiskas.json"]; 109 | 110 | actualRequest = [[LSTestRequest alloc] initWithMethod:@"PUT" url:@"https://api.example.com/cats/whiskas.json"]; 111 | 112 | [stubRequest setHeader:@"Content-Type" value:@"application/json"]; 113 | [stubRequest setHeader:@"X-API-TOKEN" value:@"123abc"]; 114 | 115 | [actualRequest setHeader:@"Content-Type" value:@"application/json"]; 116 | [actualRequest setHeader:@"X-API-TOKEN" value:@"123abc"]; 117 | }); 118 | it(@"should match", ^{ 119 | [[theValue([stubRequest matchesRequest:actualRequest]) should] beYes]; 120 | }); 121 | }); 122 | 123 | context(@"when both requests have the same body", ^{ 124 | __block LSStubRequest *stubRequest = nil; 125 | __block LSTestRequest *actualRequest = nil; 126 | beforeEach(^{ 127 | stubRequest = [[LSStubRequest alloc] initWithMethod:@"PUT" url:@"https://api.example.com/cats/whiskas.json"]; 128 | 129 | actualRequest = [[LSTestRequest alloc] initWithMethod:@"PUT" url:@"https://api.example.com/cats/whiskas.json"]; 130 | 131 | [stubRequest setHeader:@"Content-Type" value:@"application/json"]; 132 | [stubRequest setHeader:@"X-API-TOKEN" value:@"123abc"]; 133 | 134 | [actualRequest setHeader:@"Content-Type" value:@"application/json"]; 135 | [actualRequest setHeader:@"X-API-TOKEN" value:@"123abc"]; 136 | 137 | LSDataMatcher *bodyMatcher = [[LSDataMatcher alloc] initWithData:[@"Hola, this is a body" dataUsingEncoding:NSUTF8StringEncoding]]; 138 | [stubRequest setBody:bodyMatcher]; 139 | [actualRequest setBody:[@"Hola, this is a body" dataUsingEncoding:NSUTF8StringEncoding]]; 140 | }); 141 | it(@"should match", ^{ 142 | [[theValue([stubRequest matchesRequest:actualRequest]) should] beYes]; 143 | }); 144 | }); 145 | 146 | context(@"when using a matching regex to match the body", ^{ 147 | __block LSStubRequest *stubRequest = nil; 148 | __block LSTestRequest *actualRequest = nil; 149 | beforeEach(^{ 150 | stubRequest = [[LSStubRequest alloc] initWithMethod:@"PUT" url:@"https://api.example.com/cats/whiskas.json"]; 151 | 152 | actualRequest = [[LSTestRequest alloc] initWithMethod:@"PUT" url:@"https://api.example.com/cats/whiskas.json"]; 153 | 154 | [stubRequest setHeader:@"Content-Type" value:@"application/json"]; 155 | [stubRequest setHeader:@"X-API-TOKEN" value:@"123abc"]; 156 | 157 | [actualRequest setHeader:@"Content-Type" value:@"application/json"]; 158 | [actualRequest setHeader:@"X-API-TOKEN" value:@"123abc"]; 159 | 160 | LSRegexMatcher *bodyMatcher = [[LSRegexMatcher alloc] initWithRegex:[NSRegularExpression regularExpressionWithPattern:@"^Hola" options:0 error:nil]]; 161 | [stubRequest setBody:bodyMatcher]; 162 | [actualRequest setBody:[@"Hola, this is a body" dataUsingEncoding:NSUTF8StringEncoding]]; 163 | }); 164 | it(@"should match", ^{ 165 | [[theValue([stubRequest matchesRequest:actualRequest]) should] beYes]; 166 | }); 167 | }); 168 | 169 | context(@"when both requests have different bodies", ^{ 170 | __block LSStubRequest *stubRequest = nil; 171 | __block LSTestRequest *actualRequest = nil; 172 | beforeEach(^{ 173 | stubRequest = [[LSStubRequest alloc] initWithMethod:@"PUT" url:@"https://api.example.com/cats/whiskas.json"]; 174 | 175 | actualRequest = [[LSTestRequest alloc] initWithMethod:@"PUT" url:@"https://api.example.com/cats/whiskas.json"]; 176 | 177 | [stubRequest setHeader:@"Content-Type" value:@"application/json"]; 178 | [stubRequest setHeader:@"X-API-TOKEN" value:@"123abc"]; 179 | 180 | [actualRequest setHeader:@"Content-Type" value:@"application/json"]; 181 | [actualRequest setHeader:@"X-API-TOKEN" value:@"123abc"]; 182 | 183 | LSDataMatcher *bodyMatcher = [[LSDataMatcher alloc] initWithData:[@"Hola, this is a body" dataUsingEncoding:NSUTF8StringEncoding]]; 184 | [stubRequest setBody:bodyMatcher]; 185 | [actualRequest setBody:[@"Adios, this is a body as well" dataUsingEncoding:NSUTF8StringEncoding]]; 186 | }); 187 | it(@"should match", ^{ 188 | [[theValue([stubRequest matchesRequest:actualRequest]) should] beNo]; 189 | }); 190 | }); 191 | 192 | context(@"when using a regex that does no match to match the body", ^{ 193 | __block LSStubRequest *stubRequest = nil; 194 | __block LSTestRequest *actualRequest = nil; 195 | beforeEach(^{ 196 | stubRequest = [[LSStubRequest alloc] initWithMethod:@"PUT" url:@"https://api.example.com/cats/whiskas.json"]; 197 | 198 | actualRequest = [[LSTestRequest alloc] initWithMethod:@"PUT" url:@"https://api.example.com/cats/whiskas.json"]; 199 | 200 | [stubRequest setHeader:@"Content-Type" value:@"application/json"]; 201 | [stubRequest setHeader:@"X-API-TOKEN" value:@"123abc"]; 202 | 203 | [actualRequest setHeader:@"Content-Type" value:@"application/json"]; 204 | [actualRequest setHeader:@"X-API-TOKEN" value:@"123abc"]; 205 | 206 | LSRegexMatcher *bodyMatcher = [[LSRegexMatcher alloc] initWithRegex:[NSRegularExpression regularExpressionWithPattern:@"^Hola" options:0 error:nil]]; 207 | [stubRequest setBody:bodyMatcher]; 208 | [actualRequest setBody:[@"Adios, this is a body as well" dataUsingEncoding:NSUTF8StringEncoding]]; 209 | }); 210 | it(@"should match", ^{ 211 | [[theValue([stubRequest matchesRequest:actualRequest]) should] beNo]; 212 | }); 213 | }); 214 | 215 | context(@"when the stubRequest request has a nil body", ^{ 216 | __block LSStubRequest *stubRequest = nil; 217 | __block LSTestRequest *actualRequest = nil; 218 | beforeEach(^{ 219 | stubRequest = [[LSStubRequest alloc] initWithMethod:@"PUT" url:@"https://api.example.com/cats/whiskas.json"]; 220 | 221 | actualRequest = [[LSTestRequest alloc] initWithMethod:@"PUT" url:@"https://api.example.com/cats/whiskas.json"]; 222 | 223 | [stubRequest setHeader:@"Content-Type" value:@"application/json"]; 224 | [stubRequest setHeader:@"X-API-TOKEN" value:@"123abc"]; 225 | 226 | [actualRequest setHeader:@"Content-Type" value:@"application/json"]; 227 | [actualRequest setHeader:@"X-API-TOKEN" value:@"123abc"]; 228 | 229 | [actualRequest setBody:[@"Adios, this is a body as well" dataUsingEncoding:NSUTF8StringEncoding]]; 230 | }); 231 | describe(@"the stubRequest request", ^{ 232 | it(@"should match the actualRequest request", ^{ 233 | [[theValue([stubRequest matchesRequest:actualRequest]) should] beYes]; 234 | }); 235 | }); 236 | }); 237 | }); 238 | 239 | describe(@"isEqual:", ^{ 240 | it(@"is equal to a stub with the same method, url, headers and body", ^{ 241 | // It may appear that there is needless duplication of matcher objects in this tests, 242 | // however this is intended as we need to ensure the properties of the stub request 243 | // are truly equal by value, not just equal pointers. 244 | 245 | LSRegexMatcher *urlA = [[LSRegexMatcher alloc] initWithRegex:@"https://(.+)\\.example\\.com/(.+)/data\\.json".regex]; 246 | LSStubRequest *stubA = [[LSStubRequest alloc] initWithMethod:@"GET" urlMatcher:urlA]; 247 | LSRegexMatcher *urlB = [[LSRegexMatcher alloc] initWithRegex:@"https://(.+)\\.example\\.com/(.+)/data\\.json".regex]; 248 | LSStubRequest *stubB = [[LSStubRequest alloc] initWithMethod:@"GET" urlMatcher:urlB]; 249 | 250 | stubA.body = [[LSRegexMatcher alloc] initWithRegex:@"checkout my sexy body ([a-z]+?)".regex]; 251 | stubB.body = [[LSRegexMatcher alloc] initWithRegex:@"checkout my sexy body ([a-z]+?)".regex]; 252 | 253 | [stubA setHeader:@"Content-Type" value:@"application/json"]; 254 | [stubB setHeader:@"Content-Type" value:@"application/json"]; 255 | 256 | [[stubA should] equal:stubB]; 257 | }); 258 | 259 | it(@"is not equal with a different method", ^{ 260 | LSStubRequest *stubA = [[LSStubRequest alloc] initWithMethod:@"GET" url:@"google.com"]; 261 | LSStubRequest *stubB = [[LSStubRequest alloc] initWithMethod:@"POST" url:@"google.com"]; 262 | 263 | [[stubA shouldNot] equal:stubB]; 264 | }); 265 | 266 | it(@"is not equal with a different url matcher", ^{ 267 | LSRegexMatcher *urlA = [[LSRegexMatcher alloc] initWithRegex:@"https://(.+)\\.example\\.com/(.+)/data\\.json".regex]; 268 | LSStubRequest *stubA = [[LSStubRequest alloc] initWithMethod:@"GET" urlMatcher:urlA]; 269 | LSRegexMatcher *urlB = [[LSRegexMatcher alloc] initWithRegex:@"https://github\\.com/(.+)/(.+)/".regex]; 270 | LSStubRequest *stubB = [[LSStubRequest alloc] initWithMethod:@"GET" urlMatcher:urlB]; 271 | 272 | [[stubA shouldNot] equal:stubB]; 273 | }); 274 | 275 | it(@"is not equal with different headers", ^{ 276 | LSStubRequest *stubA = [[LSStubRequest alloc] initWithMethod:@"GET" url:@"google.com"]; 277 | LSStubRequest *stubB = [[LSStubRequest alloc] initWithMethod:@"GET" url:@"google.com"]; 278 | 279 | [stubA setHeader:@"Content-Type" value:@"application/json"]; 280 | [stubB setHeader:@"Content-Type" value:@"application/json"]; 281 | 282 | [stubA setHeader:@"X-OMG" value:@"nothings compares"]; 283 | [stubB setHeader:@"X-DIFFERENT" value:@"to you"]; 284 | 285 | [[stubA shouldNot] equal:stubB]; 286 | }); 287 | 288 | it(@"is not equal with a different body", ^{ 289 | LSStubRequest *stubA = [[LSStubRequest alloc] initWithMethod:@"GET" url:@"google.com"]; 290 | LSStubRequest *stubB = [[LSStubRequest alloc] initWithMethod:@"GET" url:@"google.com"]; 291 | 292 | stubA.body = [[LSStringMatcher alloc] initWithString:@"omg"]; 293 | stubB.body = [[LSStringMatcher alloc] initWithString:@"different"]; 294 | 295 | [[stubA shouldNot] equal:stubB]; 296 | }); 297 | 298 | it(@"is equal with a nil bodies", ^{ 299 | // Body is special in that it's the only propery that may be nil, 300 | // and [nil isEqual:nil] returns NO. 301 | 302 | LSStubRequest *stubA = [[LSStubRequest alloc] initWithMethod:@"GET" url:@"google.com"]; 303 | LSStubRequest *stubB = [[LSStubRequest alloc] initWithMethod:@"GET" url:@"google.com"]; 304 | 305 | stubA.body = nil; 306 | stubB.body = nil; 307 | 308 | [[stubA should] equal:stubB]; 309 | }); 310 | }); 311 | 312 | SPEC_END -------------------------------------------------------------------------------- /NocillaTests/Support/LSTestingConnection.h: -------------------------------------------------------------------------------- 1 | #import "HTTPConnection.h" 2 | #import "LSStubResponse.h" 3 | 4 | @interface LSTestingConnection : HTTPConnection 5 | @end 6 | -------------------------------------------------------------------------------- /NocillaTests/Support/LSTestingConnection.m: -------------------------------------------------------------------------------- 1 | #import "LSTestingConnection.h" 2 | #import "HTTPDataResponse.h" 3 | #import "HTTPResponse.h" 4 | @implementation LSTestingConnection 5 | - (BOOL)supportsMethod:(NSString *)method atPath:(NSString *)path { 6 | return YES; 7 | } 8 | 9 | - (NSObject *)httpResponseForMethod:(NSString *)method URI:(NSString *)path { 10 | NSObject *response = [[HTTPDataResponse alloc] initWithData:[@"hola" dataUsingEncoding:NSUTF8StringEncoding]]; 11 | 12 | return response; 13 | } 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /NocillaTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/CocoaPods/Specs.git' 2 | 3 | platform :ios, '9.0' 4 | 5 | project 'Nocilla' 6 | 7 | target :NocillaTests do 8 | inherit! :search_paths 9 | 10 | pod 'MKNetworkKit', '~> 0.87' 11 | pod 'AFNetworking', '~> 2.4.1' 12 | pod 'CocoaHTTPServer', '~> 2.2.1' 13 | pod 'Kiwi', '~> 2.3.0' 14 | pod 'ASIHTTPRequest', '>= 1.8.1' 15 | end 16 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - AFNetworking (2.4.1): 3 | - AFNetworking/NSURLConnection (= 2.4.1) 4 | - AFNetworking/NSURLSession (= 2.4.1) 5 | - AFNetworking/Reachability (= 2.4.1) 6 | - AFNetworking/Security (= 2.4.1) 7 | - AFNetworking/Serialization (= 2.4.1) 8 | - AFNetworking/UIKit (= 2.4.1) 9 | - AFNetworking/NSURLConnection (2.4.1): 10 | - AFNetworking/Reachability 11 | - AFNetworking/Security 12 | - AFNetworking/Serialization 13 | - AFNetworking/NSURLSession (2.4.1): 14 | - AFNetworking/Reachability 15 | - AFNetworking/Security 16 | - AFNetworking/Serialization 17 | - AFNetworking/Reachability (2.4.1) 18 | - AFNetworking/Security (2.4.1) 19 | - AFNetworking/Serialization (2.4.1) 20 | - AFNetworking/UIKit (2.4.1): 21 | - AFNetworking/NSURLConnection 22 | - AFNetworking/NSURLSession 23 | - ASIHTTPRequest (1.8.1): 24 | - ASIHTTPRequest/ASIWebPageRequest (= 1.8.1) 25 | - ASIHTTPRequest/CloudFiles (= 1.8.1) 26 | - ASIHTTPRequest/Core (= 1.8.1) 27 | - ASIHTTPRequest/S3 (= 1.8.1) 28 | - ASIHTTPRequest/ASIWebPageRequest (1.8.1): 29 | - ASIHTTPRequest/Core 30 | - ASIHTTPRequest/CloudFiles (1.8.1): 31 | - ASIHTTPRequest/Core 32 | - ASIHTTPRequest/Core (1.8.1): 33 | - Reachability 34 | - ASIHTTPRequest/S3 (1.8.1): 35 | - ASIHTTPRequest/Core 36 | - CocoaAsyncSocket (0.0.1) 37 | - CocoaHTTPServer (2.2.1): 38 | - CocoaAsyncSocket (~> 0.0.1) 39 | - CocoaLumberjack (~> 1.3.0) 40 | - CocoaLumberjack (1.3.3) 41 | - Kiwi (2.3.1) 42 | - MKNetworkKit (0.87): 43 | - Reachability (~> 3.1.0) 44 | - Reachability (3.1.1) 45 | 46 | DEPENDENCIES: 47 | - AFNetworking (~> 2.4.1) 48 | - ASIHTTPRequest (>= 1.8.1) 49 | - CocoaHTTPServer (~> 2.2.1) 50 | - Kiwi (~> 2.3.0) 51 | - MKNetworkKit (~> 0.87) 52 | 53 | SPEC CHECKSUMS: 54 | AFNetworking: 41d45d2c14219af0fa5addf3cfd433a002c13c9f 55 | ASIHTTPRequest: bf4f9b8be3526d40865d045bb664e0bab246fb81 56 | CocoaAsyncSocket: e2688ff99f2c2dbbeb95a1d02ba7dd878726ac83 57 | CocoaHTTPServer: 36f27f3fbc03d25a4fafcc22409a944c5bcf0d2d 58 | CocoaLumberjack: 9c1e36cb5bf886ad691191f59871a11179c7c2aa 59 | Kiwi: f038a6c61f7a9e4d7766bff5717aa3b3fdb75f55 60 | MKNetworkKit: 60ad3c83b62183e40ba75ce772c9427c25b520de 61 | Reachability: dd9aa4fb6667b9f787690a74f53cb7634ce99893 62 | 63 | PODFILE CHECKSUM: 6845bdf1d8a193a8186fdae487e0615d7037f7ba 64 | 65 | COCOAPODS: 1.0.1 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nocilla [![CI Status](http://img.shields.io/travis/luisobo/Nocilla.svg?style=flat&branch=master)](https://travis-ci.org/luisobo/Nocilla)[![Version](https://img.shields.io/cocoapods/v/Nocilla.svg?style=flat)](http://cocoadocs.org/docsets/Nocilla)[![License](https://img.shields.io/cocoapods/l/Nocilla.svg?style=flat)](http://cocoadocs.org/docsets/Nocilla)[![Platform](https://img.shields.io/cocoapods/p/Nocilla.svg?style=flat)](http://cocoadocs.org/docsets/Nocilla) 2 | 3 | Stunning HTTP stubbing for iOS and OS X. Testing HTTP requests has never been easier. 4 | 5 | This library was inspired by [WebMock](https://github.com/bblimke/webmock) and it's using [this approach](http://www.infinite-loop.dk/blog/2011/09/using-nsurlprotocol-for-injecting-test-data/) to stub the requests. 6 | 7 | ## Features 8 | * Stub HTTP and HTTPS requests in your unit tests. 9 | * Supports NSURLConnection, NSURLSession and ASIHTTPRequest. 10 | * Awesome DSL that will improve the readability and maintainability of your tests. 11 | * Match requests with regular expressions. 12 | * Stub requests with errors. 13 | * Tested. 14 | * Fast. 15 | * Extendable to support more HTTP libraries. 16 | 17 | ## Installation 18 | ### As a [CocoaPod](http://cocoapods.org/) 19 | Just add this to your Podfile 20 | ```ruby 21 | pod 'Nocilla' 22 | ``` 23 | 24 | ### Other approaches 25 | * You should be able to add Nocilla to you source tree. If you are using git, consider using a `git submodule` 26 | 27 | ## Usage 28 | _Yes, the following code is valid Objective-C, or at least, it should be_ 29 | 30 | The following examples are described using [Kiwi](https://github.com/kiwi-bdd/Kiwi) 31 | 32 | ### Common parts 33 | Until Nocilla can hook directly into Kiwi, you will have to include the following snippet in the specs you want to use Nocilla: 34 | 35 | ```objc 36 | #import "Kiwi.h" 37 | #import "Nocilla.h" 38 | SPEC_BEGIN(ExampleSpec) 39 | beforeAll(^{ 40 | [[LSNocilla sharedInstance] start]; 41 | }); 42 | afterAll(^{ 43 | [[LSNocilla sharedInstance] stop]; 44 | }); 45 | afterEach(^{ 46 | [[LSNocilla sharedInstance] clearStubs]; 47 | }); 48 | 49 | it(@"should do something", ^{ 50 | // Stub here! 51 | }); 52 | SPEC_END 53 | ``` 54 | 55 | ### Stubbing requests 56 | #### Stubbing a simple request 57 | It will return the default response, which is a 200 and an empty body. 58 | 59 | ```objc 60 | stubRequest(@"GET", @"http://www.google.com"); 61 | ``` 62 | 63 | #### Stubbing requests with regular expressions 64 | ```objc 65 | stubRequest(@"GET", @"^http://(.*?)\\.example\\.com/v1/dogs\\.json".regex); 66 | ``` 67 | 68 | 69 | #### Stubbing a request with a particular header 70 | 71 | ```objc 72 | stubRequest(@"GET", @"https://api.example.com"). 73 | withHeader(@"Accept", @"application/json"); 74 | ``` 75 | 76 | #### Stubbing a request with multiple headers 77 | 78 | Using the `withHeaders` method makes sense with the Objective-C literals, but it accepts an NSDictionary. 79 | 80 | ```objc 81 | stubRequest(@"GET", @"https://api.example.com/dogs.json"). 82 | withHeaders(@{@"Accept": @"application/json", @"X-CUSTOM-HEADER": @"abcf2fbc6abgf"}); 83 | ``` 84 | 85 | #### Stubbing a request with a particular body 86 | 87 | ```objc 88 | stubRequest(@"POST", @"https://api.example.com/dogs.json"). 89 | withHeaders(@{@"Accept": @"application/json", @"X-CUSTOM-HEADER": @"abcf2fbc6abgf"}). 90 | withBody(@"{\"name\":\"foo\"}"); 91 | ``` 92 | 93 | You can also use `NSData` for the request body: 94 | 95 | ```objc 96 | stubRequest(@"POST", @"https://api.example.com/dogs.json"). 97 | withHeaders(@{@"Accept": @"application/json", @"X-CUSTOM-HEADER": @"abcf2fbc6abgf"}). 98 | withBody([@"foo" dataUsingEncoding:NSUTF8StringEncoding]); 99 | ``` 100 | 101 | It even works with regular expressions! 102 | 103 | ```objc 104 | stubRequest(@"POST", @"https://api.example.com/dogs.json"). 105 | withHeaders(@{@"Accept": @"application/json", @"X-CUSTOM-HEADER": @"abcf2fbc6abgf"}). 106 | withBody(@"^The body start with this".regex); 107 | ``` 108 | 109 | #### Returning a specific status code 110 | ```objc 111 | stubRequest(@"GET", @"http://www.google.com").andReturn(404); 112 | ``` 113 | 114 | #### Returning a specific status code and header 115 | The same approch here, you can use `withHeader` or `withHeaders` 116 | 117 | ```objc 118 | stubRequest(@"POST", @"https://api.example.com/dogs.json"). 119 | andReturn(201). 120 | withHeaders(@{@"Content-Type": @"application/json"}); 121 | ``` 122 | 123 | #### Returning a specific status code, headers and body 124 | ```objc 125 | stubRequest(@"GET", @"https://api.example.com/dogs.json"). 126 | andReturn(201). 127 | withHeaders(@{@"Content-Type": @"application/json"}). 128 | withBody(@"{\"ok\":true}"); 129 | ``` 130 | 131 | You can also use `NSData` for the response body: 132 | 133 | ```objc 134 | stubRequest(@"GET", @"https://api.example.com/dogs.json"). 135 | andReturn(201). 136 | withHeaders(@{@"Content-Type": @"application/json"}). 137 | withBody([@"bar" dataUsingEncoding:NSUTF8StringEncoding]); 138 | ``` 139 | 140 | #### Returning raw responses recorded with `curl -is` 141 | `curl -is http://api.example.com/dogs.json > /tmp/example_curl_-is_output.txt` 142 | 143 | ```objc 144 | stubRequest(@"GET", @"https://api.example.com/dogs.json"). 145 | andReturnRawResponse([NSData dataWithContentsOfFile:@"/tmp/example_curl_-is_output.txt"]); 146 | ``` 147 | 148 | #### All together 149 | ```objc 150 | stubRequest(@"POST", @"https://api.example.com/dogs.json"). 151 | withHeaders(@{@"Accept": @"application/json", @"X-CUSTOM-HEADER": @"abcf2fbc6abgf"}). 152 | withBody(@"{\"name\":\"foo\"}"). 153 | andReturn(201). 154 | withHeaders(@{@"Content-Type": @"application/json"}). 155 | withBody(@"{\"ok\":true}"); 156 | ``` 157 | 158 | #### Making a request fail 159 | This will call the failure handler (callback, delegate... whatever your HTTP client uses) with the specified error. 160 | 161 | ```objc 162 | stubRequest(@"POST", @"https://api.example.com/dogs.json"). 163 | withHeaders(@{@"Accept": @"application/json", @"X-CUSTOM-HEADER": @"abcf2fbc6abgf"}). 164 | withBody(@"{\"name\":\"foo\"}"). 165 | andFailWithError([NSError errorWithDomain:@"foo" code:123 userInfo:nil]); 166 | ``` 167 | 168 | #### Replacing a request stub 169 | 170 | If you need to change the response of a single request, simply re-stub the request: 171 | 172 | ```objc 173 | stubRequest(@"POST", @"https://api.example.com/authorize/"). 174 | andReturn(401); 175 | 176 | // Some test expectation... 177 | 178 | stubRequest(@"POST", @"https://api.example.com/authorize/"). 179 | andReturn(200); 180 | ``` 181 | 182 | ### Unexpected requests 183 | If some request is made but it wasn't stubbed, Nocilla won't let that request hit the real world. In that case your test should fail. 184 | At this moment Nocilla will raise an exception with a meaningful message about the error and how to solve it, including a snippet of code on how to stub the unexpected request. 185 | 186 | ### Testing asynchronous requests 187 | When testing asynchrounous requests your request will be sent on a different thread from the one on which your test is executed. It is important to keep this in mind, and design your test in such a way that is has enough time to finish. For instance ```tearDown()``` when using ```XCTest``` and ```afterEach()``` when using [Quick](https://github.com/Quick/Quick) and [Nimble](https://github.com/Quick/Nimble) will cause the request never to complete. 188 | 189 | 190 | ## Who uses Nocilla. 191 | 192 | ### Submit a PR to add your company here! 193 | 194 | - [MessageBird](https://www.messagebird.com) 195 | - [Groupon](http://www.groupon.com) 196 | - [Pixable](http://www.pixable.com) 197 | - [Jackthreads](https://www.jackthreads.com) 198 | - [ShopKeep](http://www.shopkeep.com) 199 | - [Venmo](https://www.venmo.com) 200 | - [Lighthouse](http://www.lighthouselabs.co.uk) 201 | - [GE Digital](http://www.ge.com/digital/) 202 | - [Jobandtalent](http://www.jobandtalent.com) 203 | 204 | ## Other alternatives 205 | * [ILTesting](https://github.com/InfiniteLoopDK/ILTesting) 206 | * [OHHTTPStubs](https://github.com/AliSoftware/OHHTTPStubs) 207 | 208 | ## Contributing 209 | 210 | 1. Fork it 211 | 2. Create your feature branch 212 | 3. Commit your changes 213 | 4. Push to the branch 214 | 5. Create new Pull Request 215 | --------------------------------------------------------------------------------