Helper methods for testing iOS code
Objective-C Ruby
Clone or download
Latest commit dc75637 Sep 29, 2016

README.md

Forgeries

Circle CI Forgeries Logo

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.

Usage

Currently, the library provides testing replacements for:

These can be used with Dependency Injection, or by using OCMock to replace global singletons.

Gesture Recognizers

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

User Defaults

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 via hasSyncronised
  • 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.

File Manager

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

Trait Collections

You can stub the trait collections for UIView and UIViewController, the two UITraitEnvironments that we currently support.

[subject stubHorizontalSizeClass:UIUserInterfaceSizeClassRegular];

UIApplication

Nothing to out of the normal here, you can create a ForgeriesApplication which is a UIApplication subclass for DI-ing a test.

Dependency Injection

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

Requirements

Requires iOS 7 or higher.

Installation

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>

Authors

License

Forgeries is available under the MIT license. See the LICENSE file for more info.