A class to stub network requests easily: test your apps with fake network data (stubbed from file) and custom response times.
OHHTTPStubs
is very useful to write Unit Tests and return fake network data from your fixtures, or to simulate slow networks in order to check your application behavior in bad network conditions.
It works with NSURLConnection
, new iOS7/OSX.9's NSURLSession
, AFNetworking
(both 1.x and 2.x), or any networking framework that use Cocoa's URL Loading System.
You like this library? Feel free to to support its development!
- How it works
- Documentation
- Usage examples
- Advanced Usage
- Installing in your projects
- About OHHTTPStubs Unit Tests
- Change Log
- License and Credits
Using OHHTTPStubs
is as simple as calling stubRequestsPassingTest:withStubResponse:
to tell which requests you want to stub and what data you want to respond with.
For every request sent to the network, whatever the framework used (NSURLConnection
/NSURLSession
,
AFNetworking
, …):
- The block passed as first argument of
stubRequestsPassingTest:withStubResponse:
will be called to check if we need to stub this request. - If the return value of this block is YES, the block passed as second argument will be called to let you return an
OHHTTPStubsResponse
object, describing the fake response to return.
(Note: behind the scenes, it uses a custom NSURLProtocol
to intercept the requests and stub them)
OHHTTPStubs
headers are fully documented using Appledoc-like / Headerdoc-like comments in the header files.
- You can read the online documentation here;
- You can even add it as a DocSet in your Xcode Organizer or in Dash directly from the online doc mentioned above, with a simple click on the provided buttons in the top right corner;
- When you install
OHHTTPStubs
using CocoaPods, if you have appledoc installed on your Mac, you will also get the docset installed in your Xcode Organizer automatically.
Don't hesitate to take a look into OHHTTPStubsResponse.h
, OHHTTPStubsResponse+JSON.h
and OHHTTPStubsResponse.HTTPMessage.h
to see all the commodity constructors, constants and macros available.
Unfortunately macro documentation does not appear in the documentation generated by appledoc so don't forget to look the headers for those.
With the code below, every network request (because you returned YES in the first block) will return a stubbed response containing the data "Hello World!"
:
[OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) {
return YES; // Stub ALL requests without any condition
} withStubResponse:^OHHTTPStubsResponse*(NSURLRequest *request) {
// Stub all those requests with "Hello World!" string
NSData* stubData = [@"Hello World!" dataUsingEncoding:NSUTF8StringEncoding];
return [OHHTTPStubsResponse responseWithData:stubData statusCode:200 headers:nil];
}];
Note that in practice, it is not recommended to directly return YES
in the first block: you should explicitly use a computed condition (like the example below) to be sure to only stub the requests you intend to (only stub your own requests and avoid to mistakenly stub any other network requests like ones that could be done by third-party SDKs or library you use in your project)
This is typically useful in your Unit Tests to only stub specific requests targeted to a given host or WebService, for example.
With the code below, only requests to the mywebservice.com
host will be stubbed. Requests to any other host will hit the real world:
[OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) {
return [request.URL.host isEqualToString:@"mywebservice.com"];
} withStubResponse:^OHHTTPStubsResponse*(NSURLRequest *request) {
// Stub it with our "wsresponse.json" stub file
return [OHHTTPStubsResponse responseWithFileAtPath:OHPathForFileInBundle(@"wsresponse.json",nil)
statusCode:200 headers:@{@"Content-Type":@"text/json"}];
}];
This example also demonstrate how to easily return the content of a given file in your application bundle. This is useful if you have all your fixtures (stubbed responses for your Unit Tests) in your Xcode project linked with your Unit Test target.
Note: You may even put all your fixtures in a custom bundle (let's call it Fixtures.bundle) and then use the helper macros to get it with
OHPathForFileInBundle(@"wsresponse.json",OHResourceBundle(@"Fixtures"))
.
You can simulate a slow network by setting the requestTime
and/or responseTime
properties of your OHHTTPStubsResponse
.
This is useful to check that your user interface does not freeze when you have bad network conditions, and that you have all your activity indicators working while waiting for responses.
You may use the commodity chainable setters responseTime:
and requestTime:responseTime:
to set those values and easily chain method calls:
[OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) {
return [request.URL.host isEqualToString:@"mywebservice.com"];
} withStubResponse:^OHHTTPStubsResponse*(NSURLRequest *request) {
return [[OHHTTPStubsResponse responseWithJSONObject:someDict statusCode:200 headers:nil]
requestTime:1.0 responseTime:3.0];
}];
OHHTTPStubs
will wait requestTime
before sending the NSHTTPURLResponse
, and then start sending chunks of the stub data regularly during the period of responseTime
, to simulate the slow network.
At the end, you will only have the full content of your stub data after requestTime+responseTime
(time after which the completion
block or connectionDidFinishLoading:
delegate method will be called).
Note: You can specify a network speed instead of a
responseTime
by using a negative value. See below.
You may also return a network error for your stub. For example, you can easily simulate an absence of network connection like this:
[OHHTTPStubsResponse responseWithError:[NSError errorWithDomain:NSURLErrorDomain code:kCFURLErrorNotConnectedToInternet userInfo:nil]];
OHHTTPStubsResponse.h
includes a useful set of macros to build a path to your fixtures easily, like OHPathForFileInBundle
, OHPathForFileInDocumentsDir
and OHResourceBundle
. You are encouraged to use them to build your path more easily.
Especially, they use
[NSBundle bundleForClass:self.class]
to reference your app bundle (and not[NSBundle mainBundle]
as one may think), so that they still work with OCUnit and XCTestKit when unit-testing your app in the Simulator.
When building the OHHTTPStubsResponse
object, you can specify a response time (in seconds) so that the sending of the fake response will be spread over time. This allows you to simulate a slow network for example. (see "Set request and response time")
If you specify a negative value for the responseTime parameter, instead of being interpreted as a time in seconds, it will be interpreted as a download speed in KBytes/s. In that case, the response time will be computed using the size of the response's data to simulate the indicated download speed.
The OHHTTPStubsResponse
header defines some constants for standard download speeds:
OHHTTPStubsDownloadSpeedGPRS = -7 = 7 KB/s = 56 kbps
OHHTTPStubsDownloadSpeedEDGE = -16 = 16 KB/s = 128 kbps
OHHTTPStubsDownloadSpeed3G = -400 = 400 KB/s = 3200 kbps
OHHTTPStubsDownloadSpeed3GPlus = -900 = 900 KB/s = 7200 kbps
OHHTTPStubsDownloadSpeedWifi = -1500 = 1500 KB/s = 12000 kbps
Example:
return [[OHHTTPStubsResponse responseWithData:nil statusCode:400 headers:nil]
responseTime:OHHTTPStubsDownloadSpeed3G];
- You can call
stubRequestsPassingTest:withStubResponse:
multiple times. It will just add the stubs in an internal list of stubs.
This may be useful to install different stubs in various places in your code, or to separate different stubbing conditions more easily. See the OHHTTPStubsDemo
project for a typical example.
When a network request is performed by the system, the stubs are called in the reverse order that they have been added, the last added stub having priority over the first added ones.
The first stub that returns YES for the first parameter of stubRequestsPassingTest:withStubResponse:
is then used to reply to the request.
- You can remove any given stub with the
removeStub:
method. This method takes as a parameter theid<OHHTTPStubsDescriptor>
object returned bystubRequestsPassingTest:withStubResponse:
(Note: this returned object is already retained byOHHTTPStubs
while the stub is installed, so you should keep it in a__weak
variable so it is properly released from memory once removed). - You can remove the latest added stub with the
removeLastStub
method. - You can also remove all stubs at once with the
removeAllStubs
method.
This last one is useful when using OHHTTPStubs
in your Unit Tests, to remove all installed stubs at the end of each of your test case to avoid stubs installed in one test case to be still installed for the next test case.
- (void)tearDown
{
[super tearDown];
[OHHTTPStubs removeAllStubs];
}
You can add a name of your choice to your stubs. The only purpose of this is to easily identify your stubs for debugging, like when displaying them in the console.
id<OHHTTPStubsDescriptor> stub = [OHHTTPStubs stubRequestsPassingTest:... withStubResponse:...];
stub.name = @"Stub for text files";
You can even imagine applying the .name = ...
affectation directly (if you don't need to use the returned id<OHHTTPStubsDescriptor>
otherwise), for a more concise syntax:
[OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) {
...
} withStubResponse:^OHHTTPStubsResponse*(NSURLRequest *request) {
...
}].name = @"Stub for text files";
You can then list all the installed stubs using [OHHTTPStubs allStubs]
, which return an array of id<OHHTTPStubsDescriptor>
objects so you can display their name
on the console. This is useful to check that you didn't forget to remove some previous stubs that are still installed for example.
You can also setup a block to execute each time a request has been stubbed, using onStubActivation:
method, typically to log the stub being used for each request:
[OHHTTPStubs onStubActivation:^(NSURLRequest *request, id<OHHTTPStubsDescriptor> stub) {
NSLog(@"%@ stubbed by %@", request.URL, stub.name);
}];
OHHTTPSutbs
also works with iOS7's and OSX 10.9's NSURLSession
mechanism.
In general, OHHTTPStubs
is automatically enabled by default, both for:
- requests made using
NSURLConnection
or[NSURLSession sharedSession]
; - requests made using a
NSURLSession
created using a[NSURLSessionConfiguration defaultSessionConfiguration]
or[NSURLSessionConfiguration ephemeralSessionConfiguration]
configuration (using[NSURLSession sessionWithConfiguration:…]
-like methods).
If you need to disable (and re-enable) OHHTTPStubs
globally or per session, you can use:
[OHHTTPStubs setEnabled:]
forNSURLConnection
/[NSURLSession sharedSession]
-based requests[OHHTTPStubs setEnabled:forSessionConfiguration:]
for requests sent on a session created using[NSURLSession sessionWithConfiguration:...]
. Note that you have to call this before creating theNSURLSession
as theNSURLSessionConfiguration
is deep-copied on the creation of theNSURLSession
instance and cannot be modified afterwards.
In practice, there is no need to ever explicitly call setEnabled:
or setEnabled:forSessionConfiguration:
using YES
, as this is the default.
OHHTTPStubs
can't work on background sessions (sessions created using[NSURLSessionConfiguration backgroundSessionConfiguration]
) because background sessions don't allow the use of customNSURLProtocols
and are handled by the iOS Operating System itself.OHHTTPStubs
don't simulate data upload. TheNSURLProtocolClient
@protocol
does not provide a way to signal the delegate that data has been sent (only that some has been loaded), so any data in theHTTPBody
orHTTPBodyStream
of anNSURLRequest
, or data provided to-[NSURLSession uploadTaskWithRequest:fromData:];
will be ignored, and more importantly, the-URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:
delegate method will never be called when you stub the request usingOHHTTPStubs
.
As far as I know, there's nothing we can do about those two limitations. Please let me know if you know a solution that would make that possible anyway.
CocoaPods is the easiest way to add third-party libraries like OHHTTPStubs
in your projects. Simply add pod 'OHHTTPStubs'
to your Podfile
then run pod update
and you are ready to use it.
Note: OHHTTPStubs
needs iOS5 minimum.
Warning: Be careful anyway to include
OHHTTPStubs
only in your test targets, or only use it in#if DEBUG
portions, if you don't want its code and the stubbing to still be included in your release for the AppStore!
In case you don't want to use CocoaPods (but you should!!!), the OHHTTPStubs
project is provided as a Xcode project that generates a static library, so simply add its xcodeproj to your workspace and link your app against the libOHHTTPStubs.a
library. See here for detailed instructions.
Note: If you get an "unrecognised selector sent to instance" runtime error when calling one of the method declared in OHHTTPStubs
categories, make sure that the project you want to link with OHHTTPStubs
has the -ObjC
flag in its "Other Linker Flags" (OTHER_LDFLAGS
) build setting (this is normally the default in projects created in latest versions of Xcode). See the Apple doc for more details.
If you want to be able to run OHHTTPStubs
' Unit Tests, be sure you cloned the AFNetworking
submodule (by using the --recursive
option when cloning your repo, or using git submodule init
and git submodule update
) as it is used by some of OHHTTPStubs
unit tests. (This submodule is only useful for the Unit Tests testing OHHTTPStubs
with AFNetworking
: you don't need the submodule to use OHHTTPStubs
and OHHTTPStubs
has no dependency on AFNetworking
itself)
Every contribution to add more unit tests is welcome!
The changelog is available here in the dedicated wiki page. I also provide the same Release Notes on each tag/version/release directly on the GitHub Releases tab.
This project and library has been created by Olivier Halligon (@AliSoftware) and is under the MIT License.
It has been inspired by this article from InfiniteLoop.dk. I would also like to thank to @kcharwood for its contribution, and everyone who contributed to this project on GitHub.
If you want to support the development of this library, feel free to . Thanks to all contributors so far!