├── .gitignore ├── CHANGELOG.md ├── Example ├── .gitignore ├── Forgeries.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── Unit Tests.xcscheme ├── Podfile ├── Podfile.lock ├── Tests │ ├── Tests-Info.plist │ ├── Tests-Prefix.pch │ └── en.lproj │ │ └── InfoPlist.strings ├── Unit Tests │ ├── Info.plist │ └── TraitCollectionTests.m └── forgeries.xcworkspace │ └── contents.xcworkspacedata ├── Forgeries.podspec ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── Pod ├── Assets │ └── .gitkeep └── Classes │ ├── .gitkeep │ ├── Forgeries-Macros.h │ ├── ForgeriesApplication.h │ ├── ForgeriesApplication.m │ ├── ForgeriesFileManager+NotImplemented.m │ ├── ForgeriesFileManager.h │ ├── ForgeriesFileManager.m │ ├── ForgeriesGestureRecognizer.h │ ├── ForgeriesGestureRecognizer.m │ ├── ForgeriesUserDefaults.h │ ├── ForgeriesUserDefaults.m │ └── Mocks │ ├── ForgeriesFileManager+Mocks.h │ ├── ForgeriesFileManager+Mocks.m │ ├── ForgeriesTraitCollections.h │ ├── ForgeriesTraitCollections.m │ ├── ForgeriesUserDefaults+Mocks.h │ └── ForgeriesUserDefaults+Mocks.m ├── README.md ├── _Pods.xcodeproj └── circle.yml /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | # CocoaPods 21 | # 22 | # We recommend against adding the Pods directory to your .gitignore. However 23 | # you should judge for yourself, the pros and cons are mentioned at: 24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 25 | # 26 | #Pods/ 27 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.0 2 | 3 | - Fixes on gesture recognizer forgeries to return CGPoint instead of CGFloat for velocity and translation. 4 | - Adds testing_state for mocking gesture recognizer state. 5 | 6 | ## 0.6.2 7 | 8 | - Core runs without OCMock now 9 | 10 | ## 0.6.1 February 8 2016 11 | 12 | - Removes duplicate category file. 13 | 14 | ## 0.6.0 February 8 2016 15 | 16 | - New subspec layout for frameworks support. 17 | - New trait collection stubbing. 18 | 19 | ## 0.5.0 January 26 2016 20 | 21 | - NSFileManager shpport. 22 | 23 | ## 0.2.2 24 | 25 | - Adds removeObjectForKey to ForgeriesDefault 26 | 27 | ## 0.2.1 September 15 2015 28 | 29 | - Add support for CocoaPods frameworks + semi-weak OCMock linking via the `Mocks` subspec. 30 | 31 | ## 0.2.0 August 4 2015 32 | 33 | - Adds ForgeriesDefaults. 34 | 35 | ## 0.1.0 August 3 2015 36 | 37 | - Adds gesture recognizer subclasses. 38 | -------------------------------------------------------------------------------- /Example/.gitignore: -------------------------------------------------------------------------------- 1 | Pods/ 2 | -------------------------------------------------------------------------------- /Example/Forgeries.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 5E73C9351C6915B600496C7C /* TraitCollectionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E73C9341C6915B600496C7C /* TraitCollectionTests.m */; }; 11 | 6FA8D0AA21A3B04097A53FAD /* Pods_Unit_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D75A584E5C09F8B1E7E3B28B /* Pods_Unit_Tests.framework */; }; 12 | /* End PBXBuildFile section */ 13 | 14 | /* Begin PBXFileReference section */ 15 | 5E73C92C1C69156800496C7C /* Unit Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Unit Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 16 | 5E73C9301C69156800496C7C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 17 | 5E73C9341C6915B600496C7C /* TraitCollectionTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TraitCollectionTests.m; sourceTree = ""; }; 18 | 6003F58D195388D20070C39A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 19 | 6003F58F195388D20070C39A /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 20 | 6003F591195388D20070C39A /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 21 | 6003F5AF195388D20070C39A /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 22 | 8941FEFE3D9EBA0DBEB33BA8 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; 23 | 949E94C3CA21766087FE4909 /* Forgeries.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = Forgeries.podspec; path = ../Forgeries.podspec; sourceTree = ""; }; 24 | BBD21EF59DF20CEEA01B72FD /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 25 | D75A584E5C09F8B1E7E3B28B /* Pods_Unit_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Unit_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 26 | EDD2340CC6CE61664ADEEABA /* Pods-Unit Tests.debug copy.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Unit Tests.debug copy.xcconfig"; path = "Pods/Target Support Files/Pods-Unit Tests/Pods-Unit Tests.debug copy.xcconfig"; sourceTree = ""; }; 27 | /* End PBXFileReference section */ 28 | 29 | /* Begin PBXFrameworksBuildPhase section */ 30 | 5E73C9291C69156800496C7C /* Frameworks */ = { 31 | isa = PBXFrameworksBuildPhase; 32 | buildActionMask = 2147483647; 33 | files = ( 34 | 6FA8D0AA21A3B04097A53FAD /* Pods_Unit_Tests.framework in Frameworks */, 35 | ); 36 | runOnlyForDeploymentPostprocessing = 0; 37 | }; 38 | /* End PBXFrameworksBuildPhase section */ 39 | 40 | /* Begin PBXGroup section */ 41 | 25BB3B2D79D7F96B3B8E9680 /* Pods */ = { 42 | isa = PBXGroup; 43 | children = ( 44 | EDD2340CC6CE61664ADEEABA /* Pods-Unit Tests.debug copy.xcconfig */, 45 | ); 46 | name = Pods; 47 | sourceTree = ""; 48 | }; 49 | 5E73C92D1C69156800496C7C /* Unit Tests */ = { 50 | isa = PBXGroup; 51 | children = ( 52 | 5E73C9341C6915B600496C7C /* TraitCollectionTests.m */, 53 | 5E73C9301C69156800496C7C /* Info.plist */, 54 | ); 55 | path = "Unit Tests"; 56 | sourceTree = ""; 57 | }; 58 | 6003F581195388D10070C39A = { 59 | isa = PBXGroup; 60 | children = ( 61 | 60FF7A9C1954A5C5007DD14C /* Podspec Metadata */, 62 | 5E73C92D1C69156800496C7C /* Unit Tests */, 63 | 6003F58C195388D20070C39A /* Frameworks */, 64 | 6003F58B195388D20070C39A /* Products */, 65 | 25BB3B2D79D7F96B3B8E9680 /* Pods */, 66 | ); 67 | sourceTree = ""; 68 | }; 69 | 6003F58B195388D20070C39A /* Products */ = { 70 | isa = PBXGroup; 71 | children = ( 72 | 5E73C92C1C69156800496C7C /* Unit Tests.xctest */, 73 | ); 74 | name = Products; 75 | sourceTree = ""; 76 | }; 77 | 6003F58C195388D20070C39A /* Frameworks */ = { 78 | isa = PBXGroup; 79 | children = ( 80 | 6003F58D195388D20070C39A /* Foundation.framework */, 81 | 6003F58F195388D20070C39A /* CoreGraphics.framework */, 82 | 6003F591195388D20070C39A /* UIKit.framework */, 83 | 6003F5AF195388D20070C39A /* XCTest.framework */, 84 | D75A584E5C09F8B1E7E3B28B /* Pods_Unit_Tests.framework */, 85 | ); 86 | name = Frameworks; 87 | sourceTree = ""; 88 | }; 89 | 60FF7A9C1954A5C5007DD14C /* Podspec Metadata */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | 949E94C3CA21766087FE4909 /* Forgeries.podspec */, 93 | BBD21EF59DF20CEEA01B72FD /* README.md */, 94 | 8941FEFE3D9EBA0DBEB33BA8 /* LICENSE */, 95 | ); 96 | name = "Podspec Metadata"; 97 | sourceTree = ""; 98 | }; 99 | /* End PBXGroup section */ 100 | 101 | /* Begin PBXNativeTarget section */ 102 | 5E73C92B1C69156800496C7C /* Unit Tests */ = { 103 | isa = PBXNativeTarget; 104 | buildConfigurationList = 5E73C9311C69156800496C7C /* Build configuration list for PBXNativeTarget "Unit Tests" */; 105 | buildPhases = ( 106 | 17DA90F649C3385EA5DD1E63 /* [CP] Check Pods Manifest.lock */, 107 | 5E73C9281C69156800496C7C /* Sources */, 108 | 5E73C9291C69156800496C7C /* Frameworks */, 109 | 5E73C92A1C69156800496C7C /* Resources */, 110 | F209311AB0FB45107CA7F5DE /* [CP] Embed Pods Frameworks */, 111 | 74359ED5608206360195DCAB /* [CP] Copy Pods Resources */, 112 | ); 113 | buildRules = ( 114 | ); 115 | dependencies = ( 116 | ); 117 | name = "Unit Tests"; 118 | productName = "Unit Tests"; 119 | productReference = 5E73C92C1C69156800496C7C /* Unit Tests.xctest */; 120 | productType = "com.apple.product-type.bundle.unit-test"; 121 | }; 122 | /* End PBXNativeTarget section */ 123 | 124 | /* Begin PBXProject section */ 125 | 6003F582195388D10070C39A /* Project object */ = { 126 | isa = PBXProject; 127 | attributes = { 128 | CLASSPREFIX = ASH; 129 | LastUpgradeCheck = 0510; 130 | ORGANIZATIONNAME = "Ash Furrow"; 131 | TargetAttributes = { 132 | 5E73C92B1C69156800496C7C = { 133 | CreatedOnToolsVersion = 7.1; 134 | }; 135 | }; 136 | }; 137 | buildConfigurationList = 6003F585195388D10070C39A /* Build configuration list for PBXProject "Forgeries" */; 138 | compatibilityVersion = "Xcode 3.2"; 139 | developmentRegion = English; 140 | hasScannedForEncodings = 0; 141 | knownRegions = ( 142 | en, 143 | Base, 144 | ); 145 | mainGroup = 6003F581195388D10070C39A; 146 | productRefGroup = 6003F58B195388D20070C39A /* Products */; 147 | projectDirPath = ""; 148 | projectRoot = ""; 149 | targets = ( 150 | 5E73C92B1C69156800496C7C /* Unit Tests */, 151 | ); 152 | }; 153 | /* End PBXProject section */ 154 | 155 | /* Begin PBXResourcesBuildPhase section */ 156 | 5E73C92A1C69156800496C7C /* Resources */ = { 157 | isa = PBXResourcesBuildPhase; 158 | buildActionMask = 2147483647; 159 | files = ( 160 | ); 161 | runOnlyForDeploymentPostprocessing = 0; 162 | }; 163 | /* End PBXResourcesBuildPhase section */ 164 | 165 | /* Begin PBXShellScriptBuildPhase section */ 166 | 17DA90F649C3385EA5DD1E63 /* [CP] Check Pods Manifest.lock */ = { 167 | isa = PBXShellScriptBuildPhase; 168 | buildActionMask = 2147483647; 169 | files = ( 170 | ); 171 | inputPaths = ( 172 | ); 173 | name = "[CP] Check Pods Manifest.lock"; 174 | outputPaths = ( 175 | ); 176 | runOnlyForDeploymentPostprocessing = 0; 177 | shellPath = /bin/sh; 178 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; 179 | showEnvVarsInLog = 0; 180 | }; 181 | 74359ED5608206360195DCAB /* [CP] Copy Pods Resources */ = { 182 | isa = PBXShellScriptBuildPhase; 183 | buildActionMask = 2147483647; 184 | files = ( 185 | ); 186 | inputPaths = ( 187 | ); 188 | name = "[CP] Copy Pods Resources"; 189 | outputPaths = ( 190 | ); 191 | runOnlyForDeploymentPostprocessing = 0; 192 | shellPath = /bin/sh; 193 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Unit Tests/Pods-Unit Tests-resources.sh\"\n"; 194 | showEnvVarsInLog = 0; 195 | }; 196 | F209311AB0FB45107CA7F5DE /* [CP] Embed Pods Frameworks */ = { 197 | isa = PBXShellScriptBuildPhase; 198 | buildActionMask = 2147483647; 199 | files = ( 200 | ); 201 | inputPaths = ( 202 | ); 203 | name = "[CP] Embed Pods Frameworks"; 204 | outputPaths = ( 205 | ); 206 | runOnlyForDeploymentPostprocessing = 0; 207 | shellPath = /bin/sh; 208 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Unit Tests/Pods-Unit Tests-frameworks.sh\"\n"; 209 | showEnvVarsInLog = 0; 210 | }; 211 | /* End PBXShellScriptBuildPhase section */ 212 | 213 | /* Begin PBXSourcesBuildPhase section */ 214 | 5E73C9281C69156800496C7C /* Sources */ = { 215 | isa = PBXSourcesBuildPhase; 216 | buildActionMask = 2147483647; 217 | files = ( 218 | 5E73C9351C6915B600496C7C /* TraitCollectionTests.m in Sources */, 219 | ); 220 | runOnlyForDeploymentPostprocessing = 0; 221 | }; 222 | /* End PBXSourcesBuildPhase section */ 223 | 224 | /* Begin XCBuildConfiguration section */ 225 | 5E73C9321C69156800496C7C /* Debug copy */ = { 226 | isa = XCBuildConfiguration; 227 | baseConfigurationReference = EDD2340CC6CE61664ADEEABA /* Pods-Unit Tests.debug copy.xcconfig */; 228 | buildSettings = { 229 | ALWAYS_SEARCH_USER_PATHS = NO; 230 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 231 | CLANG_CXX_LIBRARY = "libc++"; 232 | CLANG_ENABLE_MODULES = YES; 233 | CLANG_ENABLE_OBJC_ARC = YES; 234 | CLANG_WARN_BOOL_CONVERSION = YES; 235 | CLANG_WARN_CONSTANT_CONVERSION = YES; 236 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 237 | CLANG_WARN_EMPTY_BODY = YES; 238 | CLANG_WARN_ENUM_CONVERSION = YES; 239 | CLANG_WARN_INT_CONVERSION = YES; 240 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 241 | CLANG_WARN_UNREACHABLE_CODE = YES; 242 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 243 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 244 | COPY_PHASE_STRIP = NO; 245 | DEBUG_INFORMATION_FORMAT = dwarf; 246 | ENABLE_STRICT_OBJC_MSGSEND = YES; 247 | ENABLE_TESTABILITY = YES; 248 | GCC_C_LANGUAGE_STANDARD = gnu99; 249 | GCC_DYNAMIC_NO_PIC = NO; 250 | GCC_NO_COMMON_BLOCKS = YES; 251 | GCC_OPTIMIZATION_LEVEL = 0; 252 | GCC_PREPROCESSOR_DEFINITIONS = ( 253 | "DEBUG=1", 254 | "$(inherited)", 255 | ); 256 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 257 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 258 | GCC_WARN_UNDECLARED_SELECTOR = YES; 259 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 260 | GCC_WARN_UNUSED_FUNCTION = YES; 261 | GCC_WARN_UNUSED_VARIABLE = YES; 262 | INFOPLIST_FILE = "Unit Tests/Info.plist"; 263 | IPHONEOS_DEPLOYMENT_TARGET = 9.1; 264 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 265 | MTL_ENABLE_DEBUG_INFO = YES; 266 | ONLY_ACTIVE_ARCH = YES; 267 | PRODUCT_BUNDLE_IDENTIFIER = "com.ashfurrow.Unit-Tests"; 268 | PRODUCT_NAME = "$(TARGET_NAME)"; 269 | SDKROOT = iphoneos; 270 | WARNING_CFLAGS = "-Wobjc-designated-initializers"; 271 | }; 272 | name = "Debug copy"; 273 | }; 274 | 5EF3D0CB1B7810ED00265146 /* Debug copy */ = { 275 | isa = XCBuildConfiguration; 276 | buildSettings = { 277 | ALWAYS_SEARCH_USER_PATHS = NO; 278 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 279 | CLANG_CXX_LIBRARY = "libc++"; 280 | CLANG_ENABLE_MODULES = YES; 281 | CLANG_ENABLE_OBJC_ARC = YES; 282 | CLANG_WARN_BOOL_CONVERSION = YES; 283 | CLANG_WARN_CONSTANT_CONVERSION = YES; 284 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 285 | CLANG_WARN_EMPTY_BODY = YES; 286 | CLANG_WARN_ENUM_CONVERSION = YES; 287 | CLANG_WARN_INT_CONVERSION = YES; 288 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 289 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 290 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 291 | COPY_PHASE_STRIP = NO; 292 | ENABLE_BITCODE = NO; 293 | GCC_C_LANGUAGE_STANDARD = gnu99; 294 | GCC_DYNAMIC_NO_PIC = NO; 295 | GCC_OPTIMIZATION_LEVEL = 0; 296 | GCC_PREPROCESSOR_DEFINITIONS = ( 297 | "DEBUG=1", 298 | "$(inherited)", 299 | ); 300 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 301 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 302 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 303 | GCC_WARN_UNDECLARED_SELECTOR = YES; 304 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 305 | GCC_WARN_UNUSED_FUNCTION = YES; 306 | GCC_WARN_UNUSED_VARIABLE = YES; 307 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 308 | ONLY_ACTIVE_ARCH = YES; 309 | SDKROOT = iphoneos; 310 | TARGETED_DEVICE_FAMILY = "1,2"; 311 | }; 312 | name = "Debug copy"; 313 | }; 314 | /* End XCBuildConfiguration section */ 315 | 316 | /* Begin XCConfigurationList section */ 317 | 5E73C9311C69156800496C7C /* Build configuration list for PBXNativeTarget "Unit Tests" */ = { 318 | isa = XCConfigurationList; 319 | buildConfigurations = ( 320 | 5E73C9321C69156800496C7C /* Debug copy */, 321 | ); 322 | defaultConfigurationIsVisible = 0; 323 | defaultConfigurationName = "Debug copy"; 324 | }; 325 | 6003F585195388D10070C39A /* Build configuration list for PBXProject "Forgeries" */ = { 326 | isa = XCConfigurationList; 327 | buildConfigurations = ( 328 | 5EF3D0CB1B7810ED00265146 /* Debug copy */, 329 | ); 330 | defaultConfigurationIsVisible = 0; 331 | defaultConfigurationName = "Debug copy"; 332 | }; 333 | /* End XCConfigurationList section */ 334 | }; 335 | rootObject = 6003F582195388D10070C39A /* Project object */; 336 | } 337 | -------------------------------------------------------------------------------- /Example/Forgeries.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/Forgeries.xcodeproj/xcshareddata/xcschemes/Unit Tests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 39 | 40 | 41 | 42 | 48 | 49 | 51 | 52 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '8.0' 2 | 3 | use_frameworks! 4 | 5 | target 'Unit Tests' do 6 | pod 'Forgeries/Mocks', :path => '../' 7 | pod 'Specta' 8 | pod 'Expecta' 9 | end -------------------------------------------------------------------------------- /Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Expecta (1.0.5) 3 | - Forgeries/Core (0.7.0) 4 | - Forgeries/Mocks (0.7.0): 5 | - Forgeries/Core 6 | - OCMock 7 | - OCMock (3.2.2) 8 | - Specta (1.0.5) 9 | 10 | DEPENDENCIES: 11 | - Expecta 12 | - Forgeries/Mocks (from `../`) 13 | - Specta 14 | 15 | EXTERNAL SOURCES: 16 | Forgeries: 17 | :path: "../" 18 | 19 | SPEC CHECKSUMS: 20 | Expecta: e1c022fcd33910b6be89c291d2775b3fe27a89fe 21 | Forgeries: 5564f52841b67b9557119a0eb029311d8b0eb746 22 | OCMock: 18c9b7e67d4c2770e95bb77a9cc1ae0c91fe3835 23 | Specta: ac94d110b865115fe60ff2c6d7281053c6f8e8a2 24 | 25 | PODFILE CHECKSUM: 883a38f30bda08974509045f3923255b2f3a3053 26 | 27 | COCOAPODS: 1.0.1 28 | -------------------------------------------------------------------------------- /Example/Tests/Tests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Example/Tests/Tests-Prefix.pch: -------------------------------------------------------------------------------- 1 | // The contents of this file are implicitly included at the beginning of every test case source file. 2 | 3 | #ifdef __OBJC__ 4 | 5 | @import Specta; 6 | @import Expecta; 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /Example/Tests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Example/Unit Tests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Example/Unit Tests/TraitCollectionTests.m: -------------------------------------------------------------------------------- 1 | @import UIKit; 2 | @import Specta; 3 | @import Expecta; 4 | @import Forgeries; 5 | 6 | @interface Test_UIViewController : UIViewController 7 | @property (nonatomic, assign) BOOL calledDidChange; 8 | @end 9 | 10 | @interface Test_UIView : UIView 11 | @property (nonatomic, assign) BOOL calledDidChange; 12 | @end 13 | 14 | SharedExamplesBegin(TraitCollection) 15 | // Global shared examples are shared across all spec files. 16 | 17 | sharedExamplesFor(@"trait collection", ^(NSDictionary *data) { 18 | __block Class class; 19 | 20 | beforeEach(^{ 21 | class = data[@"class"]; 22 | }); 23 | 24 | it(@"calls traitCollectionDidChange:", ^{ 25 | id subject = [class new]; 26 | 27 | [subject stubTraitCollection:[UITraitCollection traitCollectionWithTraitsFromCollections:@[]]]; 28 | 29 | expect([subject calledDidChange]).to.beTruthy(); 30 | }); 31 | 32 | it(@"calls stubs horizontal size class", ^{ 33 | id subject = [class new]; 34 | 35 | [subject stubHorizontalSizeClass:UIUserInterfaceSizeClassRegular]; 36 | 37 | expect([[subject traitCollection] horizontalSizeClass]).to.equal(UIUserInterfaceSizeClassRegular); 38 | 39 | }); 40 | 41 | it(@"calls stubs vertical size class", ^{ 42 | id subject = [class new]; 43 | 44 | [subject stubVerticalSizeClass:UIUserInterfaceSizeClassRegular]; 45 | 46 | expect([[subject traitCollection] verticalSizeClass]).to.equal(UIUserInterfaceSizeClassRegular); 47 | 48 | }); 49 | 50 | it(@"calls stubs horizontal and vertical size classes", ^{ 51 | id subject = [class new]; 52 | 53 | [subject stubHorizontalSizeClass:UIUserInterfaceSizeClassCompact verticalSizeClass: UIUserInterfaceSizeClassRegular]; 54 | 55 | expect([[subject traitCollection] horizontalSizeClass]).to.equal(UIUserInterfaceSizeClassCompact); 56 | expect([[subject traitCollection] verticalSizeClass]).to.equal(UIUserInterfaceSizeClassRegular); 57 | 58 | }); 59 | }); 60 | 61 | SharedExamplesEnd 62 | 63 | SpecBegin(TraitCollection) 64 | 65 | itShouldBehaveLike(@"trait collection", @{@"class" : [Test_UIViewController class]}); 66 | itShouldBehaveLike(@"trait collection", @{@"class" : [Test_UIView class]}); 67 | 68 | SpecEnd 69 | 70 | 71 | @implementation Test_UIViewController 72 | 73 | - (void)traitCollectionDidChange:(nullable UITraitCollection *)previousTraitCollection 74 | { 75 | [super traitCollectionDidChange:previousTraitCollection]; 76 | self.calledDidChange = YES; 77 | } 78 | 79 | @end 80 | 81 | @implementation Test_UIView 82 | 83 | - (void)traitCollectionDidChange:(nullable UITraitCollection *)previousTraitCollection 84 | { 85 | [super traitCollectionDidChange:previousTraitCollection]; 86 | self.calledDidChange = YES; 87 | } 88 | 89 | @end 90 | -------------------------------------------------------------------------------- /Example/forgeries.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Forgeries.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "Forgeries" 3 | s.version = "1.0.0" 4 | s.summary = "Smart subclasses for testing UIKit code." 5 | s.description = <<-DESC 6 | Forgeries is a library that makes unit testing iOS applications easier. UIKit has lots of limitations 7 | that make sense in production code, but make testing difficult. Forgeries fixes that problem. Currently 8 | it includes subclasses for UIGestureRecognizers, NSUserDefaults and NSFileManager. 9 | DESC 10 | s.homepage = "https://github.com/ashfurrow/Forgeries" 11 | s.license = 'MIT' 12 | s.authors = { "Ash Furrow" => "ash@ashfurrow.com", 13 | "Orta Therox" => "orta.therox@gmail.com" } 14 | s.source = { :git => "https://github.com/ashfurrow/Forgeries.git", :tag => s.version } 15 | s.social_media_url = 'https://twitter.com/ashfurrow' 16 | s.platform = :ios, '8.0' 17 | s.default_subspec = "Core" 18 | 19 | # To ensure that it isn't called in App code 20 | s.frameworks = 'Foundation', 'XCTest' 21 | 22 | s.subspec "Core" do |ss| 23 | ss.source_files = 'Pod/Classes/*' 24 | ss.public_header_files = 'Pod/Classes/*.h' 25 | end 26 | 27 | s.subspec "Mocks" do |ss| 28 | # Allow CP frameworks to specifically connect the two: 29 | ss.dependency "OCMock" 30 | ss.dependency "Forgeries/Core" 31 | ss.source_files = 'Pod/Classes/Mocks/*' 32 | ss.public_header_files = 'Pod/Classes/Mocks/*.h' 33 | end 34 | 35 | end 36 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem 'cocoapods', '~> 1.0.0' 4 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | activesupport (5.0.0.1) 5 | concurrent-ruby (~> 1.0, >= 1.0.2) 6 | i18n (~> 0.7) 7 | minitest (~> 5.1) 8 | tzinfo (~> 1.1) 9 | claide (1.0.0) 10 | cocoapods (1.0.1) 11 | activesupport (>= 4.0.2) 12 | claide (>= 1.0.0, < 2.0) 13 | cocoapods-core (= 1.0.1) 14 | cocoapods-deintegrate (>= 1.0.0, < 2.0) 15 | cocoapods-downloader (>= 1.0.0, < 2.0) 16 | cocoapods-plugins (>= 1.0.0, < 2.0) 17 | cocoapods-search (>= 1.0.0, < 2.0) 18 | cocoapods-stats (>= 1.0.0, < 2.0) 19 | cocoapods-trunk (>= 1.0.0, < 2.0) 20 | cocoapods-try (>= 1.0.0, < 2.0) 21 | colored (~> 1.2) 22 | escape (~> 0.0.4) 23 | fourflusher (~> 0.3.0) 24 | molinillo (~> 0.4.5) 25 | nap (~> 1.0) 26 | xcodeproj (>= 1.1.0, < 2.0) 27 | cocoapods-core (1.0.1) 28 | activesupport (>= 4.0.2) 29 | fuzzy_match (~> 2.0.4) 30 | nap (~> 1.0) 31 | cocoapods-deintegrate (1.0.1) 32 | cocoapods-downloader (1.1.1) 33 | cocoapods-plugins (1.0.0) 34 | nap 35 | cocoapods-search (1.0.0) 36 | cocoapods-stats (1.0.0) 37 | cocoapods-trunk (1.0.0) 38 | nap (>= 0.8, < 2.0) 39 | netrc (= 0.7.8) 40 | cocoapods-try (1.1.0) 41 | colored (1.2) 42 | concurrent-ruby (1.0.2) 43 | escape (0.0.4) 44 | fourflusher (0.3.2) 45 | fuzzy_match (2.0.4) 46 | i18n (0.7.0) 47 | minitest (5.9.1) 48 | molinillo (0.4.5) 49 | nap (1.1.0) 50 | netrc (0.7.8) 51 | thread_safe (0.3.5) 52 | tzinfo (1.2.2) 53 | thread_safe (~> 0.1) 54 | xcodeproj (1.3.1) 55 | activesupport (>= 3) 56 | claide (>= 1.0.0, < 2.0) 57 | colored (~> 1.2) 58 | 59 | PLATFORMS 60 | ruby 61 | 62 | DEPENDENCIES 63 | cocoapods (~> 1.0.0) 64 | 65 | BUNDLED WITH 66 | 1.12.5 67 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Ash Furrow 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Pod/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashfurrow/Forgeries/dc75637a3ce6b136515b96a829ca7af5fee704b5/Pod/Assets/.gitkeep -------------------------------------------------------------------------------- /Pod/Classes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashfurrow/Forgeries/dc75637a3ce6b136515b96a829ca7af5fee704b5/Pod/Classes/.gitkeep -------------------------------------------------------------------------------- /Pod/Classes/Forgeries-Macros.h: -------------------------------------------------------------------------------- 1 | #define NotYetImplmented NSLog(@"\n\n\n !!!!!!"); @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in the Forgery\n\n\n", __PRETTY_FUNCTION__] userInfo:nil]; __builtin_unreachable(); 2 | -------------------------------------------------------------------------------- /Pod/Classes/ForgeriesApplication.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | NS_ASSUME_NONNULL_BEGIN 4 | 5 | @interface ForgeriesApplication : UIApplication 6 | @end 7 | 8 | NS_ASSUME_NONNULL_END 9 | -------------------------------------------------------------------------------- /Pod/Classes/ForgeriesApplication.m: -------------------------------------------------------------------------------- 1 | #import "Forgeries-Macros.h" 2 | #import "ForgeriesApplication.h" 3 | 4 | @interface ForgeriesApplication() 5 | @property(nonatomic,getter=isStatusBarHidden) BOOL statusBarHidden; 6 | @end 7 | 8 | @implementation ForgeriesApplication 9 | 10 | @synthesize statusBarHidden=ourStatusBarHidden; 11 | 12 | /// We can't call the super init function 13 | /// or it will raise 14 | 15 | #pragma clang diagnostic push 16 | #pragma clang diagnostic ignored "-Wobjc-designated-initializers" 17 | - (instancetype)init 18 | { 19 | return self; 20 | } 21 | #pragma clang diagnostic pop 22 | 23 | - (BOOL)statusBarHidden 24 | { 25 | return ourStatusBarHidden; 26 | } 27 | 28 | - (BOOL)isStatusBarHidden 29 | { 30 | return ourStatusBarHidden; 31 | } 32 | 33 | - (void)setStatusBarHidden:(BOOL)statusBarHidden 34 | { 35 | ourStatusBarHidden = statusBarHidden; 36 | } 37 | 38 | - (void)setStatusBarHidden:(BOOL)hidden withAnimation:(UIStatusBarAnimation)animation 39 | { 40 | self.statusBarHidden = hidden; 41 | } 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /Pod/Classes/ForgeriesFileManager+NotImplemented.m: -------------------------------------------------------------------------------- 1 | #import "ForgeriesFileManager.h" 2 | #import "Forgeries-Macros.h" 3 | 4 | @interface ForgeriesFileManager(NotImplemented) 5 | @end 6 | 7 | @implementation ForgeriesFileManager (NotImplemented) 8 | 9 | - (nullable NSArray *)mountedVolumeURLsIncludingResourceValuesForKeys:(nullable NSArray *)propertyKeys options:(NSVolumeEnumerationOptions)options { NotYetImplmented; } 10 | 11 | - (void)unmountVolumeAtURL:(NSURL *)url options:(NSFileManagerUnmountOptions)mask completionHandler:(void (^)(NSError * __nullable errorOrNil))completionHandler { NotYetImplmented; }; 12 | 13 | - (nullable NSArray *)contentsOfDirectoryAtURL:(NSURL *)url includingPropertiesForKeys:(nullable NSArray *)keys options:(NSDirectoryEnumerationOptions)mask error:(NSError **)error { NotYetImplmented; }; 14 | 15 | 16 | - (nullable NSURL *)URLForDirectory:(NSSearchPathDirectory)directory inDomain:(NSSearchPathDomainMask)domain appropriateForURL:(nullable NSURL *)url create:(BOOL)shouldCreate error:(NSError **)error { NotYetImplmented; } 17 | 18 | - (BOOL)getRelationship:(NSURLRelationship *)outRelationship ofDirectoryAtURL:(NSURL *)directoryURL toItemAtURL:(NSURL *)otherURL error:(NSError **)error { NotYetImplmented; } 19 | 20 | - (BOOL)getRelationship:(NSURLRelationship *)outRelationship ofDirectory:(NSSearchPathDirectory)directory inDomain:(NSSearchPathDomainMask)domainMask toItemAtURL:(NSURL *)url error:(NSError **)error { NotYetImplmented; } 21 | 22 | - (BOOL)createDirectoryAtURL:(NSURL *)url withIntermediateDirectories:(BOOL)createIntermediates attributes:(nullable NSDictionary *)attributes error:(NSError **)error { NotYetImplmented; } 23 | 24 | - (BOOL)createSymbolicLinkAtURL:(NSURL *)url withDestinationURL:(NSURL *)destURL error:(NSError **)error { NotYetImplmented; } 25 | 26 | - (BOOL)setAttributes:(NSDictionary *)attributes ofItemAtPath:(NSString *)path error:(NSError **)error { NotYetImplmented; } 27 | 28 | - (BOOL)createDirectoryAtPath:(NSString *)path withIntermediateDirectories:(BOOL)createIntermediates attributes:(nullable NSDictionary *)attributes error:(NSError **)error { NotYetImplmented; } 29 | 30 | - (nullable NSArray *)contentsOfDirectoryAtPath:(NSString *)path error:(NSError **)error { NotYetImplmented; } 31 | 32 | - (nullable NSArray *)subpathsOfDirectoryAtPath:(NSString *)path error:(NSError **)error { NotYetImplmented; } 33 | 34 | - (nullable NSDictionary *)attributesOfItemAtPath:(NSString *)path error:(NSError **)error { NotYetImplmented; } 35 | 36 | - (nullable NSDictionary *)attributesOfFileSystemForPath:(NSString *)path error:(NSError **)error { NotYetImplmented; } 37 | 38 | 39 | - (BOOL)createSymbolicLinkAtPath:(NSString *)path withDestinationPath:(NSString *)destPath error:(NSError **)error { NotYetImplmented; } 40 | 41 | - (nullable NSString *)destinationOfSymbolicLinkAtPath:(NSString *)path error:(NSError **)error { NotYetImplmented; } 42 | 43 | - (BOOL)copyItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath error:(NSError **)error { NotYetImplmented; } 44 | - (BOOL)moveItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath error:(NSError **)error { NotYetImplmented; } 45 | - (BOOL)linkItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath error:(NSError **)error { NotYetImplmented; } 46 | - (BOOL)removeItemAtPath:(NSString *)path error:(NSError **)error { NotYetImplmented; } 47 | 48 | - (BOOL)copyItemAtURL:(NSURL *)srcURL toURL:(NSURL *)dstURL error:(NSError **)error { NotYetImplmented; } 49 | - (BOOL)moveItemAtURL:(NSURL *)srcURL toURL:(NSURL *)dstURL error:(NSError **)error { NotYetImplmented; } 50 | - (BOOL)linkItemAtURL:(NSURL *)srcURL toURL:(NSURL *)dstURL error:(NSError **)error { NotYetImplmented; } 51 | - (BOOL)removeItemAtURL:(NSURL *)URL error:(NSError **)error { NotYetImplmented; } 52 | 53 | 54 | - (BOOL)trashItemAtURL:(NSURL *)url resultingItemURL:(NSURL * __nullable * __nullable)outResultingURL error:(NSError **)error { NotYetImplmented; } 55 | 56 | 57 | - (BOOL)changeCurrentDirectoryPath:(NSString *)path { NotYetImplmented; } 58 | 59 | 60 | - (BOOL)fileExistsAtPath:(NSString *)path isDirectory:(nullable BOOL *)isDirectory { NotYetImplmented; } 61 | - (BOOL)isReadableFileAtPath:(NSString *)path { NotYetImplmented; } 62 | - (BOOL)isWritableFileAtPath:(NSString *)path { NotYetImplmented; } 63 | - (BOOL)isExecutableFileAtPath:(NSString *)path { NotYetImplmented; } 64 | - (BOOL)isDeletableFileAtPath:(NSString *)path { NotYetImplmented; } 65 | 66 | - (BOOL)contentsEqualAtPath:(NSString *)path1 andPath:(NSString *)path2 { NotYetImplmented; } 67 | 68 | - (NSString *)displayNameAtPath:(NSString *)path { NotYetImplmented; } 69 | 70 | - (nullable NSArray *)componentsToDisplayForPath:(NSString *)path { NotYetImplmented; } 71 | 72 | - (nullable NSDirectoryEnumerator *)enumeratorAtPath:(NSString *)path { NotYetImplmented; } 73 | 74 | - (nullable NSDirectoryEnumerator *)enumeratorAtURL:(NSURL *)url includingPropertiesForKeys:(nullable NSArray *)keys options:(NSDirectoryEnumerationOptions)mask errorHandler:(nullable BOOL (^)(NSURL *url, NSError *error))handler { NotYetImplmented; } 75 | 76 | - (nullable NSArray *)subpathsAtPath:(NSString *)path { NotYetImplmented; } 77 | 78 | - (BOOL)createFileAtPath:(NSString *)path contents:(nullable NSData *)data attributes:(nullable NSDictionary *)attr { NotYetImplmented; } 79 | 80 | - (const char *)fileSystemRepresentationWithPath:(NSString *)path { NotYetImplmented; } 81 | 82 | - (NSString *)stringWithFileSystemRepresentation:(const char *)str length:(NSUInteger)len { NotYetImplmented; } 83 | 84 | 85 | - (BOOL)replaceItemAtURL:(NSURL *)originalItemURL withItemAtURL:(NSURL *)newItemURL backupItemName:(nullable NSString *)backupItemName options:(NSFileManagerItemReplacementOptions)options resultingItemURL:(NSURL * __nullable * __nullable)resultingURL error:(NSError **)error { NotYetImplmented; } 86 | 87 | - (BOOL)setUbiquitous:(BOOL)flag itemAtURL:(NSURL *)url destinationURL:(NSURL *)destinationURL error:(NSError **)error { NotYetImplmented; } 88 | 89 | - (BOOL)isUbiquitousItemAtURL:(NSURL *)url { NotYetImplmented; } 90 | 91 | - (BOOL)startDownloadingUbiquitousItemAtURL:(NSURL *)url error:(NSError **)error { NotYetImplmented; } 92 | 93 | - (BOOL)evictUbiquitousItemAtURL:(NSURL *)url error:(NSError **)error { NotYetImplmented; } 94 | 95 | - (nullable NSURL *)URLForUbiquityContainerIdentifier:(nullable NSString *)containerIdentifier { NotYetImplmented; } 96 | 97 | - (nullable NSURL *)URLForPublishingUbiquitousItemAtURL:(NSURL *)url expirationDate:(NSDate * __nullable * __nullable)outDate error:(NSError **)error { NotYetImplmented; } 98 | 99 | 100 | - (nullable NSURL *)containerURLForSecurityApplicationGroupIdentifier:(NSString *)groupIdentifier { NotYetImplmented; } 101 | 102 | 103 | 104 | @end 105 | -------------------------------------------------------------------------------- /Pod/Classes/ForgeriesFileManager.h: -------------------------------------------------------------------------------- 1 | NS_ASSUME_NONNULL_BEGIN 2 | 3 | @interface ForgeryFile : NSObject 4 | @property (nonatomic, copy) NSString *path; 5 | @property (nonatomic, strong) NSData *data; 6 | @end 7 | 8 | /// Provides a NSFileManager compatible API but allows you to have an 9 | /// in-memory lookup store with a simple dictionary based file system API 10 | 11 | @interface ForgeriesFileManager : NSFileManager 12 | 13 | /// This will generate a file mapping where it will generate the `ForgeryFile`s for you 14 | /// based on the mapping dict you provide. Values can be NSString or NSDictionaries. 15 | 16 | /// The filemap supports shortcuts, to make it easier to deal with paths: 17 | /// - App Bundle : @"/app/thing.txt" 18 | /// - User Documents : @"/docs/thing.txt" 19 | /// 20 | + (instancetype)withFileStringMap:(NSDictionary *)dictionary; 21 | 22 | /// Just offering up the full dictionary of the underlying file mapping data, 23 | /// as this is for testing, more introspective power is a priority 24 | @property (nonatomic, strong) NSMutableDictionary *fileMap; 25 | 26 | @end 27 | 28 | NS_ASSUME_NONNULL_END 29 | -------------------------------------------------------------------------------- /Pod/Classes/ForgeriesFileManager.m: -------------------------------------------------------------------------------- 1 | #import "Forgeries-Macros.h" 2 | #import "ForgeriesFileManager.h" 3 | 4 | @implementation ForgeryFile 5 | 6 | - (NSURL *)urlRepresentation 7 | { 8 | return [NSURL fileURLWithPath:self.path]; 9 | } 10 | 11 | @end 12 | 13 | @implementation ForgeriesFileManager 14 | 15 | + (instancetype)withFileStringMap:(NSDictionary *)dictionary 16 | { 17 | ForgeriesFileManager *fileManager = [[ForgeriesFileManager alloc] init]; 18 | 19 | NSMutableDictionary *fileMap = [NSMutableDictionary dictionary]; 20 | for (NSString *key in dictionary.allKeys) { 21 | ForgeryFile *file = [[ForgeryFile alloc] init]; 22 | file.path = key; 23 | id object = dictionary[key]; 24 | 25 | if ([object isKindOfClass:NSString.class]) { 26 | file.data = [object dataUsingEncoding:NSUTF8StringEncoding]; 27 | } else if ([object isKindOfClass:NSDictionary.class]) { 28 | file.data = [NSJSONSerialization dataWithJSONObject:object options:NSJSONWritingPrettyPrinted error:nil]; 29 | } 30 | 31 | fileMap[key] = file; 32 | } 33 | fileManager.fileMap = fileMap; 34 | return fileManager; 35 | } 36 | 37 | - (instancetype)init 38 | { 39 | self = [super init]; 40 | if (!self) return nil; 41 | 42 | _fileMap = [NSMutableDictionary dictionary]; 43 | 44 | return self; 45 | } 46 | 47 | # pragma mark - Some Mapping functions 48 | 49 | - (NSArray *)filesMatchingPath:(NSString *)string 50 | { 51 | NSMutableArray *files = [NSMutableArray array]; 52 | for (NSString *path in self.fileMap.allKeys) { 53 | if ([path hasPrefix:[self shorthandForFilePath:string]]) { 54 | [files addObject:self.fileMap[path]]; 55 | } 56 | } 57 | return files; 58 | } 59 | 60 | - (NSArray *)urlsForFiles:(NSArray *)files 61 | { 62 | NSMutableArray *urls = [NSMutableArray array]; 63 | for (ForgeryFile *file in files) { 64 | [urls addObject:file.urlRepresentation]; 65 | } 66 | return urls; 67 | } 68 | 69 | # pragma mark - Shorthand support 70 | 71 | - (NSString *)shorthandForFilePath:(NSString *)filepath 72 | { 73 | NSString *appPath = [[NSBundle mainBundle] bundlePath]; 74 | filepath = [filepath stringByReplacingOccurrencesOfString:appPath withString:@"/app"]; 75 | return filepath; 76 | } 77 | 78 | - (NSString *)shorthandForSearchPathDirectory:(NSSearchPathDirectory)directory 79 | { 80 | switch (directory) { 81 | case NSDocumentDirectory: 82 | return @"/docs/"; 83 | default: 84 | NotYetImplmented; 85 | } 86 | } 87 | 88 | #pragma mark - Custom NSFileManager Overrides 89 | 90 | - (NSArray *)URLsForDirectory:(NSSearchPathDirectory)directory inDomains:(NSSearchPathDomainMask)domainMask 91 | { 92 | NSString *path = [self shorthandForSearchPathDirectory:directory]; 93 | return @[[NSURL fileURLWithPath:path]]; 94 | } 95 | 96 | - (BOOL)fileExistsAtPath:(NSString *)path 97 | { 98 | return [self filesMatchingPath:path].count > 0; 99 | } 100 | 101 | - (nullable NSData *)contentsAtPath:(NSString *)path 102 | { 103 | ForgeryFile *file = [self filesMatchingPath:path].firstObject; 104 | return [file data]; 105 | } 106 | @end 107 | -------------------------------------------------------------------------------- /Pod/Classes/ForgeriesGestureRecognizer.h: -------------------------------------------------------------------------------- 1 | @import UIKit; 2 | 3 | @interface UIGestureRecognizer (Forgeries) 4 | 5 | - (void)invoke; 6 | 7 | @end 8 | 9 | #define ForgeryUIGestureRecognizerSubclass(name, superclass) \ 10 | @interface name : superclass \ 11 | \ 12 | @property (nonatomic, readwrite) CGPoint testing_location; \ 13 | @property (nonatomic, readwrite) CGPoint testing_velocity; \ 14 | @property (nonatomic, readwrite) CGPoint testing_translation; \ 15 | @property (nonatomic, readwrite) UIGestureRecognizerState testing_state; \ 16 | \ 17 | @end 18 | 19 | ForgeryUIGestureRecognizerSubclass(ForgeryTapGestureRecognizer, UITapGestureRecognizer) 20 | ForgeryUIGestureRecognizerSubclass(ForgeryPinchGestureRecognizer, UIPinchGestureRecognizer) 21 | ForgeryUIGestureRecognizerSubclass(ForgeryRotationGestureRecognizer, UIRotationGestureRecognizer) 22 | ForgeryUIGestureRecognizerSubclass(ForgerySwipeGestureRecognizer, UISwipeGestureRecognizer) 23 | ForgeryUIGestureRecognizerSubclass(ForgeryPanGestureRecognizer, UIPanGestureRecognizer) 24 | ForgeryUIGestureRecognizerSubclass(ForgeryScreenEdgeGestureRecognizer, UIScreenEdgePanGestureRecognizer) 25 | ForgeryUIGestureRecognizerSubclass(ForgeryLongPressGestureRecognizer, UILongPressGestureRecognizer) 26 | -------------------------------------------------------------------------------- /Pod/Classes/ForgeriesGestureRecognizer.m: -------------------------------------------------------------------------------- 1 | #import "ForgeriesGestureRecognizer.h" 2 | #import 3 | 4 | void *ForgeryGestureRecognizerTargetActionsKey = &ForgeryGestureRecognizerTargetActionsKey; 5 | 6 | @interface TargetAction : NSObject 7 | 8 | @property (nonatomic, weak) id target; 9 | @property (nonatomic, assign) SEL action; 10 | 11 | - (instancetype)initWithTarget:(id)target action:(SEL)action; 12 | 13 | @end 14 | 15 | @implementation TargetAction 16 | 17 | - (instancetype)initWithTarget:(id)target action:(SEL)action { 18 | self = [super init]; 19 | if (self == nil) return nil; 20 | 21 | self.target = target; 22 | self.action = action; 23 | 24 | return self; 25 | } 26 | 27 | @end 28 | 29 | @implementation UIGestureRecognizer (Forgeries) 30 | 31 | - (NSMutableArray *)targetActionArray { 32 | NSMutableArray *array = objc_getAssociatedObject(self, ForgeryGestureRecognizerTargetActionsKey); 33 | 34 | if (array == nil) { 35 | array = [NSMutableArray array]; 36 | 37 | objc_setAssociatedObject(self, ForgeryGestureRecognizerTargetActionsKey, array, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 38 | } 39 | 40 | return array; 41 | } 42 | 43 | #pragma clang diagnostic push 44 | #pragma clang diagnostic ignored "-Warc-performSelector-leaks" 45 | - (void)invoke { 46 | for (TargetAction *pair in self.targetActionArray) { 47 | if ([NSStringFromSelector(pair.action) hasSuffix:@":"]) { 48 | [pair.target performSelector:pair.action withObject:self]; 49 | } else { 50 | [pair.target performSelector:pair.action]; 51 | } 52 | } 53 | } 54 | #pragma clang diagnostic pop 55 | 56 | - (void)addTestingTarget:(id)target action:(SEL)action { 57 | [self.targetActionArray addObject:[[TargetAction alloc] initWithTarget:target action:action]]; 58 | } 59 | 60 | - (void)removeTestingTarget:(id)target action:(SEL)action { 61 | if (target == nil || action == nil) { 62 | [self.targetActionArray removeAllObjects]; 63 | } else { 64 | NSArray *pairsToRemove = [self.targetActionArray filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(TargetAction *pair, NSDictionary *bindings) { 65 | return pair.target == target && pair.action == action; 66 | }]]; 67 | 68 | [self.targetActionArray removeObjectsInArray:pairsToRemove]; 69 | } 70 | } 71 | 72 | @end 73 | 74 | #define ForgeryUIGestureRecognizerSubclassImplementation(name) \ 75 | @implementation name \ 76 | @synthesize state; \ 77 | - (instancetype)initWithTarget:(id)target action:(SEL)action { \ 78 | self = [super initWithTarget:target action:action]; \ 79 | if (self != nil) { \ 80 | [self addTestingTarget:target action:action]; \ 81 | } \ 82 | return self; \ 83 | } \ 84 | - (void)addTarget:(id)target action:(SEL)action { \ 85 | [super addTarget:target action:action]; \ 86 | [self addTestingTarget:target action:action]; \ 87 | \ 88 | } \ 89 | - (void)removeTarget:(id)target action:(SEL)action { \ 90 | [super removeTarget:target action:action]; \ 91 | [self removeTestingTarget:target action:action]; \ 92 | } \ 93 | - (CGPoint)locationInView:(UIView *)view { \ 94 | return self.testing_location; \ 95 | } \ 96 | - (CGPoint)velocityInView:(UIView *)view { \ 97 | return self.testing_velocity; \ 98 | } \ 99 | - (CGPoint)translationInView:(UIView *)view { \ 100 | return self.testing_translation; \ 101 | } \ 102 | - (UIGestureRecognizerState)state {\ 103 | return self.testing_state; \ 104 | } \ 105 | @end 106 | 107 | ForgeryUIGestureRecognizerSubclassImplementation(ForgeryTapGestureRecognizer) 108 | ForgeryUIGestureRecognizerSubclassImplementation(ForgeryPinchGestureRecognizer) 109 | ForgeryUIGestureRecognizerSubclassImplementation(ForgeryRotationGestureRecognizer) 110 | ForgeryUIGestureRecognizerSubclassImplementation(ForgerySwipeGestureRecognizer) 111 | ForgeryUIGestureRecognizerSubclassImplementation(ForgeryPanGestureRecognizer) 112 | ForgeryUIGestureRecognizerSubclassImplementation(ForgeryScreenEdgeGestureRecognizer) 113 | ForgeryUIGestureRecognizerSubclassImplementation(ForgeryLongPressGestureRecognizer) 114 | -------------------------------------------------------------------------------- /Pod/Classes/ForgeriesUserDefaults.h: -------------------------------------------------------------------------------- 1 | /// Provides a NSUserDefault compatible API but is 2 | /// much simpler, and allows for easier introspection 3 | 4 | @interface ForgeriesUserDefaults : NSObject 5 | 6 | /// Returns a defaults based on a dictionary. 7 | + (instancetype)defaults:(NSDictionary *)dictionary; 8 | 9 | /// Sets a bool on the underlying mutable dictionary 10 | - (void)setBool:(BOOL)value forKey:(id)key; 11 | 12 | /// Sets an object to the underlying mutable dictionary 13 | - (void)setObject:(id)object forKey:(id)key; 14 | 15 | /// Returns a bool value from the underlying mutable dictionary 16 | - (BOOL)boolForKey:(id)key; 17 | 18 | /// Returns a string array from the underlying mutable dictionary 19 | /// presumably someone uses this under the hood, 'cause I 20 | /// didn't know it existed. 21 | - (NSArray *)stringArrayForKey:(id)key; 22 | 23 | /// Returns a string value from the underlying mutable dictionary 24 | - (NSString *)stringForKey:(id)key; 25 | 26 | /// Returns a value from the underlying mutable dictionary 27 | - (id)objectForKey:(id)key; 28 | 29 | /// Returns a integer value from the underlying mutable dictionary 30 | - (NSInteger)integerForKey:(NSString *)defaultName; 31 | 32 | /// Removes a value from the underlying mutable dictionary 33 | - (void)removeObjectForKey:(id)key; 34 | 35 | /// Find out the last key that was set on the defaults 36 | @property (nonatomic, copy) id lastSetKey; 37 | 38 | /// Find out the last key that was requested on the default 39 | @property (nonatomic, copy) id lastRequestedKey; 40 | 41 | /// Just offering up the full dictionary of the underlying data, 42 | /// as this is for testing, more introspective power is a priority 43 | @property (nonatomic, strong) NSMutableDictionary *defaults; 44 | 45 | /// Sets `hasSyncronised` to true, basically a NO-OP for compatability 46 | - (void)synchronize; 47 | 48 | /// Set to false by default, so you can verify you've called it 49 | @property (nonatomic, assign) BOOL hasSyncronised; 50 | 51 | /// Easier introspection, basically forwards to `defaults` 52 | - (id)objectForKeyedSubscript:(id)key; 53 | 54 | /// Alias'd to `setObject:forKey:`. 55 | - (void)setObject:(id)obj forKeyedSubscript:(id)key; 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /Pod/Classes/ForgeriesUserDefaults.m: -------------------------------------------------------------------------------- 1 | #import "ForgeriesUserDefaults.h" 2 | 3 | @interface ForgeriesUserDefaults () 4 | // This is used by QLPreviewController for something. 5 | @property (nonatomic, assign) BOOL QLDisableQuicklookd; 6 | @end 7 | 8 | 9 | @implementation ForgeriesUserDefaults 10 | 11 | + (instancetype)defaults:(NSDictionary *)dictionary 12 | { 13 | ForgeriesUserDefaults *defaults = [[ForgeriesUserDefaults alloc] init]; 14 | defaults.defaults = [dictionary mutableCopy]; 15 | return defaults; 16 | } 17 | 18 | - (instancetype)init 19 | { 20 | self = [super init]; 21 | if (!self) return nil; 22 | 23 | _defaults = [NSMutableDictionary dictionary]; 24 | 25 | return self; 26 | } 27 | 28 | - (void)setBool:(BOOL)value forKey:(id)key 29 | { 30 | [self setObject:@(value) forKey:key]; 31 | } 32 | 33 | - (void)setObject:(id)object forKey:(id)key 34 | { 35 | self.lastSetKey = key; 36 | self.defaults[key] = object; 37 | } 38 | 39 | - (BOOL)boolForKey:(id)key 40 | { 41 | self.lastRequestedKey = key; 42 | BOOL value = [[self objectForKey:key] boolValue]; 43 | return value; 44 | } 45 | 46 | - (id)objectForKey:(id)key 47 | { 48 | self.lastRequestedKey = key; 49 | return self.defaults[key]; 50 | } 51 | 52 | - (NSString *)stringForKey:(id)key 53 | { 54 | id object = [self objectForKey:key]; 55 | return [object isKindOfClass:NSString.class] ? object : nil; 56 | } 57 | 58 | - (NSArray *)arrayForKey:(id)key 59 | { 60 | id object = [self objectForKey:key]; 61 | return [object isKindOfClass:NSArray.class] ? object : nil; 62 | } 63 | 64 | - (NSInteger)integerForKey:(NSString *)defaultName 65 | { 66 | id object = [self objectForKey:defaultName]; 67 | return [object isKindOfClass:NSNumber.class] ? [object integerValue] : 0; 68 | } 69 | 70 | - (NSArray *)stringArrayForKey:(id)key 71 | { 72 | id object = [self objectForKey:key]; 73 | return [object isKindOfClass:NSArray.class] ? object : nil; 74 | } 75 | 76 | - (id)objectForKeyedSubscript:(id)key 77 | { 78 | return [self objectForKey:key]; 79 | } 80 | 81 | - (void)setObject:(id)obj forKeyedSubscript:(id)key 82 | { 83 | [self setObject:obj forKey:key]; 84 | } 85 | 86 | - (void)synchronize 87 | { 88 | self.hasSyncronised = YES; 89 | } 90 | 91 | - (void)registerDefaults:(NSDictionary *)dictionary 92 | { 93 | [_defaults addEntriesFromDictionary:dictionary]; 94 | } 95 | 96 | - (void)removeObjectForKey:(id)key 97 | { 98 | [self.defaults removeObjectForKey:key]; 99 | } 100 | 101 | - (NSDictionary *)persistentDomainForName:(NSString *)domainName 102 | { 103 | return _defaults; 104 | } 105 | 106 | - (NSDictionary *)dictionaryRepresentation 107 | { 108 | return _defaults.copy; 109 | } 110 | 111 | @end 112 | -------------------------------------------------------------------------------- /Pod/Classes/Mocks/ForgeriesFileManager+Mocks.h: -------------------------------------------------------------------------------- 1 | 2 | #import "ForgeriesFileManager.h" 3 | 4 | NS_ASSUME_NONNULL_BEGIN 5 | 6 | /// Provides a NSFileManager compatible API but allows you to have an 7 | /// in-memory lookup store with a simple dictionary based file system API 8 | 9 | @interface ForgeriesFileManager (Mocks) 10 | 11 | /// Replaces the NSUserDefault standardUserDefaults method with a ForgeriesUserDefaults 12 | + (id)replaceDefaultFileManager; 13 | /// The returned method when the above is called 14 | + (instancetype)defaultManager; 15 | 16 | @end 17 | 18 | NS_ASSUME_NONNULL_END 19 | -------------------------------------------------------------------------------- /Pod/Classes/Mocks/ForgeriesFileManager+Mocks.m: -------------------------------------------------------------------------------- 1 | #import "Forgeries-Macros.h" 2 | #import "ForgeriesFileManager+Mocks.h" 3 | #import 4 | 5 | @implementation ForgeriesFileManager (Mocks) 6 | 7 | + (id)replaceDefaultFileManager 8 | { 9 | OCMockObject *mockClass = [OCMockObject niceMockForClass:NSFileManager.class]; 10 | ForgeriesFileManager *manager = [ForgeriesFileManager withFileStringMap:@{}]; 11 | [[[mockClass stub] andReturn:manager] defaultManager]; 12 | return mockClass; 13 | } 14 | 15 | + (instancetype)defaultManager 16 | { 17 | return (id)[NSFileManager defaultManager]; 18 | } 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /Pod/Classes/Mocks/ForgeriesTraitCollections.h: -------------------------------------------------------------------------------- 1 | @import UIKit; 2 | 3 | #define ExtendTraitEnvironment(specific_class) \ 4 | @interface specific_class (Forgeries) \ 5 | - (void)stubTraitCollection:(UITraitCollection *)traitCollection; \ 6 | \ 7 | - (void)stubHorizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass; \ 8 | \ 9 | - (void)stubVerticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass; \ 10 | \ 11 | - (void)stubHorizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass; \ 12 | @end 13 | 14 | ExtendTraitEnvironment(UIViewController) 15 | ExtendTraitEnvironment(UIView) 16 | -------------------------------------------------------------------------------- /Pod/Classes/Mocks/ForgeriesTraitCollections.m: -------------------------------------------------------------------------------- 1 | #import "ForgeriesTraitCollections.h" 2 | #import 3 | 4 | void stubTraitCollectionInEnvironment(UITraitCollection *traitCollection, id environment) { 5 | id partialMock = [OCMockObject partialMockForObject:environment]; 6 | [[[partialMock stub] andReturn:traitCollection] traitCollection]; 7 | [environment traitCollectionDidChange:nil]; 8 | } 9 | 10 | #define ExtendTraitEnvironmentImplementation(specific_class) \ 11 | @implementation specific_class (Forgeries) \ 12 | - (void)stubTraitCollection:(UITraitCollection *)traitCollection \ 13 | { \ 14 | stubTraitCollectionInEnvironment(traitCollection, self); \ 15 | } \ 16 | - (void)stubHorizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass \ 17 | { \ 18 | [self stubTraitCollection:[UITraitCollection traitCollectionWithHorizontalSizeClass:horizontalSizeClass]]; \ 19 | } \ 20 | - (void)stubVerticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass \ 21 | { \ 22 | [self stubTraitCollection:[UITraitCollection traitCollectionWithVerticalSizeClass:verticalSizeClass]]; \ 23 | } \ 24 | - (void)stubHorizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass \ 25 | { \ 26 | UITraitCollection *traitCollection = [UITraitCollection traitCollectionWithTraitsFromCollections:@[ \ 27 | [UITraitCollection traitCollectionWithHorizontalSizeClass:horizontalSizeClass], \ 28 | [UITraitCollection traitCollectionWithVerticalSizeClass:verticalSizeClass] \ 29 | ]]; \ 30 | [self stubTraitCollection:traitCollection]; \ 31 | } \ 32 | @end 33 | 34 | ExtendTraitEnvironmentImplementation(UIViewController) 35 | ExtendTraitEnvironmentImplementation(UIView) 36 | -------------------------------------------------------------------------------- /Pod/Classes/Mocks/ForgeriesUserDefaults+Mocks.h: -------------------------------------------------------------------------------- 1 | #import "ForgeriesUserDefaults.h" 2 | 3 | /// Provides a NSUserDefault compatible API but is 4 | /// much simpler, and allows for easier introspection 5 | 6 | @interface ForgeriesUserDefaults (Mocks) 7 | 8 | /// Replaces the NSUserDefault standardUserDefaults method with a ForgeriesUserDefaults 9 | + (id)replaceStandardUserDefaultsWith:(NSDictionary *)dictionary; 10 | /// The returned method when the above is called 11 | + (instancetype)standardUserDefaults; 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Pod/Classes/Mocks/ForgeriesUserDefaults+Mocks.m: -------------------------------------------------------------------------------- 1 | #import "ForgeriesUserDefaults+Mocks.h" 2 | #import 3 | 4 | @implementation ForgeriesUserDefaults (Mocks) 5 | 6 | + (id)replaceStandardUserDefaultsWith:(NSDictionary *)dictionary 7 | { 8 | OCMockObject *mockClass = [OCMockObject niceMockForClass:NSUserDefaults.class]; 9 | ForgeriesUserDefaults *defaults = [ForgeriesUserDefaults defaults:dictionary]; 10 | [[[mockClass stub] andReturn:defaults] standardUserDefaults]; 11 | return mockClass; 12 | } 13 | 14 | + (instancetype)standardUserDefaults 15 | { 16 | return (id)[NSUserDefaults standardUserDefaults]; 17 | } 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Forgeries 2 | 3 | [![Circle CI](https://circleci.com/gh/ashfurrow/Forgeries.svg?style=svg)](https://circleci.com/gh/ashfurrow/Forgeries) 4 | ![Forgeries Logo](https://cloud.githubusercontent.com/assets/49038/12582759/f6a2c294-c436-11e5-9ee1-aea3e7256240.jpg) 5 | 6 | Forgeries is a library that makes unit testing iOS applications easier. UIKit has lots of limitations 7 | that make sense in production code, but make testing difficult. Forgeries fixes that problem. 8 | 9 | ## Usage 10 | 11 | Currently, the library provides testing replacements for: 12 | 13 | - [Standard gesture recognizers](#gesture-recognizers) 14 | - [NSUserDefaults](#user-defaults) 15 | - [NSFileManager](#file-manager) 16 | - [View trait collections](#trait-collections) 17 | - [UIApplication](#uiapplication) 18 | 19 | 20 | These can be used with [Dependency Injection](#dependency-injection), or by using OCMock to replace global singletons. 21 | 22 | ### Gesture Recognizers 23 | 24 | The following are Forgeries' subclasses for gesture recognizers. 25 | 26 | - ForgeryTapGestureRecognizer 27 | - ForgeryPinchGestureRecognizer 28 | - ForgeryRotationGestureRecognizer 29 | - ForgerySwipeGestureRecognizer 30 | - ForgeryPanGestureRecognizer 31 | - ForgeryScreenEdgeGestureRecognizer 32 | - ForgeryLongPressGestureRecognizer 33 | 34 | These subclasses keep track of the number of times they've invoked their targets' actions; a handy interface to `UIGestureRecognizer` is provided: 35 | 36 | ```objc 37 | @interface UIGestureRecognizer (Forgeries) 38 | 39 | - (void)invoke; 40 | 41 | @end 42 | ``` 43 | 44 | ### User Defaults 45 | 46 | `ForgeriesUserDefaults` is a class which is API compatible with NSUserDefaults. It has a few extra tools that make it useful for testing: 47 | 48 | - A quick API `[ForgeriesUserDefaults defaults:@{}]` for setting up defaults from a dictionary 49 | - APIs for inspecting the `lastSetKey`, `lastRequestedKey` and whether it has been synchronised via `hasSyncronised` 50 | - Offers a subscripting interface so you can easily edit the defaults instance 51 | - Can replace `[NSUserDefaults standardUserDefaults]` when OCMock is available in the test target 52 | 53 | _Note this class isn't yet a subclass of NSUserDefaults, and so cannot be DI'd in to Swift classes._ 54 | 55 | ### File Manager 56 | 57 | `ForgeriesFileManager` is still new, so it's API is relatively limited as we find more use cases for it. 58 | 59 | - A quick API for setting up defaults from a dictionary 60 | ``` objc 61 | ForgeriesFileManager *fm = [ForgeriesFileManager withFileStringMap:@{ 62 | @"/docs/EchoTest.json" : @{ @"updated_at" : @"2001-01-23" }, 63 | @"/app/EchoTest.json": @{ @"updated_at" : @"1985-01-23" }, 64 | @"/docs/VERSION" : @"1.0.1" 65 | }]; 66 | ``` 67 | 68 | - This API will automatically convert dictionaries to raw JSON data, or let you create files with text 69 | - Uses an in-memory store for file lookup, and accessing data. Faster, and won't change per-developer 70 | - Is a subclass of NSFileManager, with functions it doesn't support raising exceptions. Help us add more functions. 71 | - Can replace `[NSFileManager defaultManager]` when OCMock is available in the test target 72 | 73 | ### Trait Collections 74 | 75 | You can stub the trait collections for `UIView` and `UIViewController`, the two `UITraitEnvironments` that we currently support. 76 | 77 | ```objc 78 | [subject stubHorizontalSizeClass:UIUserInterfaceSizeClassRegular]; 79 | ``` 80 | 81 | ### UIApplication 82 | 83 | Nothing to out of the normal here, you can create a `ForgeriesApplication` which is a UIApplication subclass for DI-ing a test. 84 | 85 | ### Dependency Injection 86 | 87 | 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. 88 | 89 | Say you're testing `MyViewController`, you'd use lazy loading for your recognizer. 90 | 91 | ```objc 92 | @interface MyViewController () 93 | 94 | @property (nonatomic, strong) UITapGestureRecognizer *recognizer; 95 | 96 | @end 97 | 98 | @implementation MyViewController 99 | 100 | ... 101 | 102 | - (UITapGestureRecognizer *)recognizer { 103 | if (_recognizer == nil) { 104 | _recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleGestureRecognizer:)]; 105 | } 106 | 107 | return _recognizer; 108 | } 109 | 110 | ... 111 | ``` 112 | 113 | What we need to do is set that property before it is lazily loaded. Here's our testing code: 114 | 115 | ```objc 116 | MyViewController *subject = /* Instantiate somehow */ 117 | ForgeryTapGestureRecognizer *recognizer = [[ForgeryTapGestureRecognizer alloc] initWithTarget:subject action:@selector(handleGestureRecognizer:)]; 118 | subject.recognizer = recognizer; 119 | 120 | /* Optionally, set the testing_location and testing_velocity properties on recognizer. */ 121 | 122 | [recognizer invoke]; 123 | 124 | expect(subject).to( /* have done something, whatever it is you're testing for */ ); 125 | ``` 126 | 127 | If you're interested in dependency injection, we strongly recommend watching this [talk from Jon Reid](http://qualitycoding.org/dependency-injection/) 128 | 129 | ## Requirements 130 | 131 | Requires iOS 7 or higher. 132 | 133 | ## Installation 134 | 135 | Forgeries is available through [CocoaPods](http://cocoapods.org). To install it, simply add the following line to your Podfile under the _unit testing_ target: 136 | 137 | ```ruby 138 | target 'MyApp_Tests' do 139 | inherit! :search_paths 140 | 141 | pod 'Forgeries' 142 | ... 143 | end 144 | ``` 145 | 146 | That will import the core functionality, not including mock stuff. If you want to use Forgeries with OCMock, use the following instead: 147 | 148 | ```ruby 149 | pod 'Forgeries/Mocks' 150 | ``` 151 | 152 | Now import the library in your unit tests. 153 | 154 | ```swift 155 | import Forgeries 156 | ``` 157 | 158 | ```objc 159 | @import Forgeries; 160 | // or #import 161 | ``` 162 | 163 | ## Authors 164 | 165 | - Ash Furrow, ash@ashfurrow.com 166 | - Orta Therox, orta.therox@gmail.com 167 | 168 | ## License 169 | 170 | Forgeries is available under the MIT license. See the LICENSE file for more info. 171 | -------------------------------------------------------------------------------- /_Pods.xcodeproj: -------------------------------------------------------------------------------- 1 | Example/Pods/Pods.xcodeproj -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | xcode: 3 | version: "7.1" 4 | test: 5 | override: 6 | - xctool 7 | -workspace 'Example/Forgeries.xcworkspace' 8 | -scheme 'Unit Tests' 9 | -sdk iphonesimulator 10 | -destination 'platform=iOS Simulator,name=iPhone 6,OS=latest' 11 | -reporter plain:$CIRCLE_ARTIFACTS/xctool.log 12 | -reporter junit:$CIRCLE_TEST_REPORTS/xcode/results.xml 13 | test 14 | --------------------------------------------------------------------------------