Forgeries is a library that makes unit testing iOS applications easier. UIKit has lots of limitations that make sense in production code, but make testing difficult. Forgeries fixes that problem.
Currently, the library provides testing replacements for:
These can be used with Dependency Injection, or by using OCMock to replace global singletons.
The following are Forgeries' subclasses for gesture recognizers.
- ForgeryTapGestureRecognizer
- ForgeryPinchGestureRecognizer
- ForgeryRotationGestureRecognizer
- ForgerySwipeGestureRecognizer
- ForgeryPanGestureRecognizer
- ForgeryScreenEdgeGestureRecognizer
- ForgeryLongPressGestureRecognizer
These subclasses keep track of the number of times they've invoked their targets' actions; a handy interface to UIGestureRecognizer
is provided:
@interface UIGestureRecognizer (Forgeries)
- (void)invoke;
@end
ForgeriesUserDefaults
is a class which is API compatible with NSUserDefaults. It has a few extra tools that make it useful for testing:
- A quick API
[ForgeriesUserDefaults defaults:@{}]
for setting up defaults from a dictionary - APIs for inspecting the
lastSetKey
,lastRequestedKey
and whether it has been synchronised viahasSyncronised
- Offers a subscripting interface so you can easily edit the defaults instance
- Can replace
[NSUserDefaults standardUserDefaults]
when OCMock is available in the test target
Note this class isn't yet a subclass of NSUserDefaults, and so cannot be DI'd in to Swift classes.
ForgeriesFileManager
is still new, so it's API is relatively limited as we find more use cases for it.
-
A quick API for setting up defaults from a dictionary
ForgeriesFileManager *fm = [ForgeriesFileManager withFileStringMap:@{ @"/docs/EchoTest.json" : @{ @"updated_at" : @"2001-01-23" }, @"/app/EchoTest.json": @{ @"updated_at" : @"1985-01-23" }, @"/docs/VERSION" : @"1.0.1" }];
-
This API will automatically convert dictionaries to raw JSON data, or let you create files with text
-
Uses an in-memory store for file lookup, and accessing data. Faster, and won't change per-developer
-
Is a subclass of NSFileManager, with functions it doesn't support raising exceptions. Help us add more functions.
-
Can replace
[NSFileManager defaultManager]
when OCMock is available in the test target
You can stub the trait collections for UIView
and UIViewController
, the two UITraitEnvironments
that we currently support.
[subject stubHorizontalSizeClass:UIUserInterfaceSizeClassRegular];
Nothing to out of the normal here, you can create a ForgeriesApplication
which is a UIApplication subclass for DI-ing a test.
The trick is to use Forgeries in testing only. A great way to do this is via Dependency Injection. This means injecting a dependency into an instance, instead of having that instance create the dependency itself, or access shared state. Let's take a look at an example.
Say you're testing MyViewController
, you'd use lazy loading for your recognizer.
@interface MyViewController ()
@property (nonatomic, strong) UITapGestureRecognizer *recognizer;
@end
@implementation MyViewController
...
- (UITapGestureRecognizer *)recognizer {
if (_recognizer == nil) {
_recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleGestureRecognizer:)];
}
return _recognizer;
}
...
What we need to do is set that property before it is lazily loaded. Here's our testing code:
MyViewController *subject = /* Instantiate somehow */
ForgeryTapGestureRecognizer *recognizer = [[ForgeryTapGestureRecognizer alloc] initWithTarget:subject action:@selector(handleGestureRecognizer:)];
subject.recognizer = recognizer;
/* Optionally, set the testing_location and testing_velocity properties on recognizer. */
[recognizer invoke];
expect(subject).to( /* have done something, whatever it is you're testing for */ );
If you're interested in dependency injection, we strongly recommend watching this talk from Jon Reid
Requires iOS 7 or higher.
Forgeries is available through CocoaPods. To install it, simply add the following line to your Podfile under the unit testing target:
target 'MyApp_Tests' do
inherit! :search_paths
pod 'Forgeries'
...
end
That will import the core functionality, not including mock stuff. If you want to use Forgeries with OCMock, use the following instead:
pod 'Forgeries/Mocks'
Now import the library in your unit tests.
import Forgeries
@import Forgeries;
// or #import <Forgeries/Forgeries.h>
- Ash Furrow, [email protected]
- Orta Therox, [email protected]
Forgeries is available under the MIT license. See the LICENSE file for more info.