Skip to content

Commit

Permalink
Merge pull request #60 from luisobo/nsurlsession-hook
Browse files Browse the repository at this point in the history
[WIP] Hook for NSURLSession
  • Loading branch information
luisobo committed Jan 18, 2014
2 parents 1cca2be + f179784 commit a8fff06
Show file tree
Hide file tree
Showing 8 changed files with 141 additions and 18 deletions.
33 changes: 30 additions & 3 deletions Nocilla.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@
A02C7AFE15EE62650061FCEE /* NSURLRequest+LSHTTPRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = A0EE631415ECD8B900009A08 /* NSURLRequest+LSHTTPRequest.m */; };
A02C7B0515EE63540061FCEE /* NSURLRequest+DSL.h in Headers */ = {isa = PBXBuildFile; fileRef = A02C7B0315EE63530061FCEE /* NSURLRequest+DSL.h */; };
A02C7B0615EE63540061FCEE /* NSURLRequest+DSL.m in Sources */ = {isa = PBXBuildFile; fileRef = A02C7B0415EE63540061FCEE /* NSURLRequest+DSL.m */; };
A04D23C5187E3BF200EF6CB2 /* NSURLSessionStubbingSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = A04D23C4187E3BF200EF6CB2 /* NSURLSessionStubbingSpec.m */; };
A04D23C9187E4BC900EF6CB2 /* LSNSURLSessionHook.h in Headers */ = {isa = PBXBuildFile; fileRef = A04D23C7187E4BC900EF6CB2 /* LSNSURLSessionHook.h */; };
A04D23CA187E4BC900EF6CB2 /* LSNSURLSessionHook.m in Sources */ = {isa = PBXBuildFile; fileRef = A04D23C8187E4BC900EF6CB2 /* LSNSURLSessionHook.m */; };
A0708D9115E6EC9F00352085 /* LSHTTPStubURLProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = A0708D8F15E6EC9F00352085 /* LSHTTPStubURLProtocol.h */; };
A0708D9415E6FC4100352085 /* LSHTTPStubURLProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = A0708D9015E6EC9F00352085 /* LSHTTPStubURLProtocol.m */; };
A0708DA615E6FFB800352085 /* LSHTTPClientHook.h in Headers */ = {isa = PBXBuildFile; fileRef = A0708DA415E6FFB800352085 /* LSHTTPClientHook.h */; settings = {ATTRIBUTES = (Public, ); }; };
Expand Down Expand Up @@ -142,6 +145,9 @@
A02C7AF715EE5CF80061FCEE /* LSHTTPRequestDSLRepresentation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = LSHTTPRequestDSLRepresentation.m; path = DSL/LSHTTPRequestDSLRepresentation.m; sourceTree = "<group>"; };
A02C7B0315EE63530061FCEE /* NSURLRequest+DSL.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSURLRequest+DSL.h"; path = "Hooks/NSURLRequest/NSURLRequest+DSL.h"; sourceTree = "<group>"; };
A02C7B0415EE63540061FCEE /* NSURLRequest+DSL.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSURLRequest+DSL.m"; path = "Hooks/NSURLRequest/NSURLRequest+DSL.m"; sourceTree = "<group>"; };
A04D23C4187E3BF200EF6CB2 /* NSURLSessionStubbingSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = NSURLSessionStubbingSpec.m; path = Hooks/NSURLSession/NSURLSessionStubbingSpec.m; sourceTree = "<group>"; };
A04D23C7187E4BC900EF6CB2 /* LSNSURLSessionHook.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = LSNSURLSessionHook.h; path = Hooks/NSURLSession/LSNSURLSessionHook.h; sourceTree = "<group>"; };
A04D23C8187E4BC900EF6CB2 /* LSNSURLSessionHook.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = LSNSURLSessionHook.m; path = Hooks/NSURLSession/LSNSURLSessionHook.m; sourceTree = "<group>"; };
A060B22415FE680900BEAD54 /* LSHTTPStubURLProtocolSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = LSHTTPStubURLProtocolSpec.m; path = Hooks/NSURLRequest/LSHTTPStubURLProtocolSpec.m; sourceTree = "<group>"; };
A0708D8F15E6EC9F00352085 /* LSHTTPStubURLProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = LSHTTPStubURLProtocol.h; path = Hooks/NSURLRequest/LSHTTPStubURLProtocol.h; sourceTree = "<group>"; };
A0708D9015E6EC9F00352085 /* LSHTTPStubURLProtocol.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = LSHTTPStubURLProtocol.m; path = Hooks/NSURLRequest/LSHTTPStubURLProtocol.m; sourceTree = "<group>"; };
Expand Down Expand Up @@ -309,6 +315,7 @@
children = (
A01CEBDC175FFE0300637CE7 /* ASIHTTPRequest */,
A02C7B0C15EE65EB0061FCEE /* NSURLRequest */,
A04D23C3187E3BA800EF6CB2 /* NSURLSession */,
);
name = Hooks;
sourceTree = "<group>";
Expand All @@ -333,9 +340,27 @@
name = Diff;
sourceTree = "<group>";
};
A04D23C3187E3BA800EF6CB2 /* NSURLSession */ = {
isa = PBXGroup;
children = (
A04D23C4187E3BF200EF6CB2 /* NSURLSessionStubbingSpec.m */,
);
name = NSURLSession;
sourceTree = "<group>";
};
A04D23C6187E4B9500EF6CB2 /* NSURLSession */ = {
isa = PBXGroup;
children = (
A04D23C7187E4BC900EF6CB2 /* LSNSURLSessionHook.h */,
A04D23C8187E4BC900EF6CB2 /* LSNSURLSessionHook.m */,
);
name = NSURLSession;
sourceTree = "<group>";
};
A0708DA215E6FF7200352085 /* Hooks */ = {
isa = PBXGroup;
children = (
A04D23C6187E4B9500EF6CB2 /* NSURLSession */,
A01CEBDF1760009300637CE7 /* ASIHTTPRequest */,
A0708DA415E6FFB800352085 /* LSHTTPClientHook.h */,
A0708DA515E6FFB800352085 /* LSHTTPClientHook.m */,
Expand Down Expand Up @@ -497,6 +522,7 @@
A02889311728C52100288022 /* LSMatcher.h in Headers */,
A02889391728C95600288022 /* NSRegularExpression+Matcheable.h in Headers */,
A028893E1728CA5900288022 /* NSString+Nocilla.h in Headers */,
A04D23C9187E4BC900EF6CB2 /* LSNSURLSessionHook.h in Headers */,
A0708DA615E6FFB800352085 /* LSHTTPClientHook.h in Headers */,
A0708DAA15E7008200352085 /* LSNSURLHook.h in Headers */,
A01CEBE2176000CB00637CE7 /* LSASIHTTPRequestHook.h in Headers */,
Expand Down Expand Up @@ -564,7 +590,7 @@
A085B82F15E30749007D33C1 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0440;
LastUpgradeCheck = 0500;
ORGANIZATIONNAME = "Luis Solano Bonet";
};
buildConfigurationList = A085B83215E30749007D33C1 /* Build configuration list for PBXProject "Nocilla" */;
Expand Down Expand Up @@ -649,6 +675,7 @@
files = (
A074ABB115E30B0000730EE5 /* LSNocilla.m in Sources */,
A074ABB315E30B0000730EE5 /* LSStubRequest.m in Sources */,
A04D23CA187E4BC900EF6CB2 /* LSNSURLSessionHook.m in Sources */,
A074ABB515E30B0000730EE5 /* LSStubResponse.m in Sources */,
A0708D9415E6FC4100352085 /* LSHTTPStubURLProtocol.m in Sources */,
A0708DA715E6FFB800352085 /* LSHTTPClientHook.m in Sources */,
Expand Down Expand Up @@ -682,6 +709,7 @@
A0055EE815EEAF8C005D6C85 /* LSTestingConnection.m in Sources */,
A0BB870115FE7C2E0090236F /* AFNetworkingStubbingSpec.m in Sources */,
A0BB870215FE7C320090236F /* NSURLRequestHookSpec.m in Sources */,
A04D23C5187E3BF200EF6CB2 /* NSURLSessionStubbingSpec.m in Sources */,
A0BB870315FE7C350090236F /* LSHTTPStubURLProtocolSpec.m in Sources */,
A0BB870615FE7D8F0090236F /* LSStubRequestSpec.m in Sources */,
0FA3E7FE1680775B001DB582 /* LSStubResponseSpec.m in Sources */,
Expand Down Expand Up @@ -720,7 +748,6 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ARCHS = "$(ARCHS_STANDARD_32_BIT)";
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
Expand All @@ -738,6 +765,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 6.0;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
};
name = Debug;
Expand All @@ -746,7 +774,6 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ARCHS = "$(ARCHS_STANDARD_32_BIT)";
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
Expand Down
4 changes: 2 additions & 2 deletions Nocilla.xcodeproj/xcshareddata/xcschemes/Nocilla.xcscheme
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0440"
LastUpgradeVersion = "0500"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down Expand Up @@ -42,7 +42,7 @@
buildForAnalyzing = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "CD0E54BEB6E7492C882EB7D1"
BlueprintIdentifier = "02256C81EDE34E779286B8FF"
BuildableName = "libPods-NocillaTests.a"
BlueprintName = "Pods-NocillaTests"
ReferencedContainer = "container:Pods/Pods.xcodeproj">
Expand Down
15 changes: 15 additions & 0 deletions Nocilla/Hooks/NSURLSession/LSNSURLSessionHook.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// LSNSURLSessionHook.h
// Nocilla
//
// Created by Luis Solano Bonet on 08/01/14.
// Copyright (c) 2014 Luis Solano Bonet. All rights reserved.
//

#import <Nocilla/Nocilla.h>

#import "LSHTTPClientHook.h"

@interface LSNSURLSessionHook : LSHTTPClientHook

@end
38 changes: 38 additions & 0 deletions Nocilla/Hooks/NSURLSession/LSNSURLSessionHook.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//
// LSNSURLSessionHook.m
// Nocilla
//
// Created by Luis Solano Bonet on 08/01/14.
// Copyright (c) 2014 Luis Solano Bonet. All rights reserved.
//

#import "LSNSURLSessionHook.h"
#import "LSHTTPStubURLProtocol.h"
#import <objc/runtime.h>

@implementation LSNSURLSessionHook

- (void)load {
[self swizzleSelector:@selector(protocolClasses) fromClass:NSClassFromString(@"__NSCFURLSessionConfiguration") toClass:[self class]];
}

- (void)unload {
[self swizzleSelector:@selector(protocolClasses) fromClass:NSClassFromString(@"__NSCFURLSessionConfiguration") toClass:[self class]];
}

- (void)swizzleSelector:(SEL)selector fromClass:(Class)original toClass:(Class)stub {

Method originalMethod = class_getInstanceMethod(original, selector);
Method stubMethod = class_getInstanceMethod(stub, selector);
if (!originalMethod || !stubMethod) {
[NSException raise:NSInternalInconsistencyException format:@"Couldn't load NSURLSession hook."];
}
method_exchangeImplementations(originalMethod, stubMethod);
}

- (NSArray *)protocolClasses {
return @[[LSHTTPStubURLProtocol class]];
}


@end
6 changes: 5 additions & 1 deletion Nocilla/LSNocilla.m
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#import "LSStubRequest.h"
#import "LSHTTPRequestDSLRepresentation.h"
#import "LSASIHTTPRequestHook.h"
#import "LSNSURLSessionHook.h"
#import "LSASIHTTPRequestHook.h"

NSString * const LSUnexpectedRequest = @"Unexpected Request";

Expand Down Expand Up @@ -32,7 +34,9 @@ - (id)init {
if (self) {
_mutableRequests = [NSMutableArray array];
_hooks = [NSMutableArray array];
[self registerHook:[[LSNSURLHook alloc] init]];
[self registerHook:[[LSNSURLHook alloc] init]];
[self registerHook:[[LSNSURLSessionHook alloc] init]];
[self registerHook:[[LSASIHTTPRequestHook alloc] init]];
}
return self;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
SPEC_BEGIN(ASIHTTPRequestStubbingSpec)

beforeEach(^{
[[LSNocilla sharedInstance] registerHook:[[LSASIHTTPRequestHook alloc] init]];
[[LSNocilla sharedInstance] start];
});

Expand Down
44 changes: 44 additions & 0 deletions NocillaTests/Hooks/NSURLSession/NSURLSessionStubbingSpec.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// NSURLSessionStubbingSpec.m
// Nocilla
//
// Created by Luis Solano Bonet on 08/01/14.
// Copyright 2014 Luis Solano Bonet. All rights reserved.
//

#import "Kiwi.h"
#import "Nocilla.h"
#import "LSHTTPStubURLProtocol.h"

SPEC_BEGIN(NSURLSessionStubbingSpec)

beforeEach(^{
[[LSNocilla sharedInstance] start];
});
afterEach(^{
[[LSNocilla sharedInstance] stop];
[[LSNocilla sharedInstance] clearStubs];
});

it(@"stubs NSURLSessionDataTask", ^{
stubRequest(@"GET", @"http://example.com")
.andReturn(200)
.withBody(@"this is a counter example.");
__block BOOL done = NO;
__block NSData *capturedData;
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];

NSURL *url = [NSURL URLWithString:@"http://example.com"];
NSURLSession *urlsession = [NSURLSession sessionWithConfiguration:configuration];
NSURLSessionDataTask *datatask = [urlsession dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
done = YES;
capturedData = data;
}];
[datatask resume];


[[expectFutureValue(theValue(done)) shouldEventually] beYes];
[[[[NSString alloc] initWithData:capturedData encoding:NSUTF8StringEncoding] should] equal:@"this is a counter example."];
});

SPEC_END
18 changes: 7 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,14 @@ This library was inspired by [WebMock](https://github.com/bblimke/webmock) and i

## Features
* Stub HTTP and HTTPS requests in your unit tests.
* Supports NSURLConnection, NSURLSession and ASIHTTPRequest.
* Awesome DSL that will improve the readability and maintainability of your tests.
* NEW! Match requests with regular expressions.
* NEW! Stub requests with errors.
* Match requests with regular expressions.
* Stub requests with errors.
* Tested.
* Fast.
* Extendable to support more HTTP libraries.

### EXPERIMENTAL: Support for ASIHTTPRequest
At this moment Nocilla supports stubbing request made with ASIHTTPRequest. This feature is experimental for the moment and more testing in the wild needs to be done. It has been tested with the classes `ASIHTTPRequest` and `ASIFormDataRequest`. It has _not_ been tested for `ASIWebPageRequest`, `ASICloudFilesRequest` or `ASIS3Request`.
If you want to enable it, point to the podspec in this repo and register the hook before starting Nocilla, like this:

```ruby
pod 'Nocilla', :podspec => 'https://raw.github.com/luisobo/Nocilla/master/Nocilla.podspec'
```
Expand Down Expand Up @@ -71,7 +68,7 @@ It will return the default response, which is a 200 and an empty body.
stubRequest(@"GET", @"http://www.google.com");
```

#### NEW! Stubbing requests with regular expressions
#### Stubbing requests with regular expressions
```objc
stubRequest(@"GET", @"^http://(.*?)\.example\.com/v1/dogs\.json".regex);
```
Expand All @@ -86,7 +83,7 @@ withHeader(@"Accept", @"application/json");

#### Stubbing a request with multiple headers

Using the `withHeaders` method makes sense with the new Objective-C literals, but it accepts an NSDictionary.
Using the `withHeaders` method makes sense with the Objective-C literals, but it accepts an NSDictionary.

```objc
stubRequest(@"GET", @"https://api.example.com/dogs.json").
Expand Down Expand Up @@ -158,7 +155,7 @@ withHeaders(@{@"Content-Type": @"application/json"}).
withBody(@"{\"ok\":true}");
```
#### NEW! Making a request fail
#### Making a request fail
This will call the failure handler (callback, delegate... whatever your HTTP client uses) with the specified error.
```objc
Expand All @@ -170,8 +167,7 @@ andFailWithError([NSError errorWithDomain:@"foo" code:123 userInfo:nil]);

### Unexpected requests
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.
At this moment Nocilla will return a response with a 500, the header `X-Nocilla: Unexpected Request` and a body with a meaningful message about the error and how to solve it, including a snippet of code on how to stub the unexpected request.
I'm not particularly happy with returning a 500 and this will change. Check [this issue](https://github.com/luisobo/Nocilla/issues/5) for more details.
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.

## Other alternatives
* [ILTesting](https://github.com/InfiniteLoopDK/ILTesting)
Expand Down

0 comments on commit a8fff06

Please sign in to comment.