FIRSwizzlerTest.m 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. // Copyright 2017 Google
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #import <XCTest/XCTest.h>
  15. #import <objc/runtime.h>
  16. #import "FirebaseCommunity/FIRSwizzler.h"
  17. @interface TestObject : NSObject
  18. @end
  19. @implementation TestObject
  20. + (NSString *)description {
  21. return [[super description] stringByAppendingString:@" and here's my addition: BLAH BLAH"];
  22. }
  23. // This method is used to help test swizzling a method that calls super.
  24. - (NSString *)description {
  25. return [NSString stringWithFormat:@"TestObject, superclass: %@", [super description]];
  26. }
  27. @end
  28. @interface TestObjectSubclass : TestObject
  29. @end
  30. @implementation TestObjectSubclass
  31. @end
  32. @interface FIRSwizzlerTest : XCTestCase
  33. @end
  34. @implementation FIRSwizzlerTest
  35. /** Tests originalImplementationForClass:selector:isClassSelector: returns the original instance
  36. * IMP.
  37. */
  38. - (void)testOriginalImpInstanceMethod {
  39. Method method = class_getInstanceMethod([NSObject class], @selector(description));
  40. IMP originalImp = method_getImplementation(method);
  41. NSString * (^newImplementation)() = ^NSString *() {
  42. return @"nonsense";
  43. };
  44. [FIRSwizzler swizzleClass:[NSObject class]
  45. selector:@selector(description)
  46. isClassSelector:NO
  47. withBlock:newImplementation];
  48. IMP returnedImp = [FIRSwizzler originalImplementationForClass:[NSObject class]
  49. selector:@selector(description)
  50. isClassSelector:NO];
  51. XCTAssertEqual(returnedImp, originalImp);
  52. [FIRSwizzler unswizzleClass:[NSObject class] selector:@selector(description) isClassSelector:NO];
  53. }
  54. /** Tests that invoking an original IMP in a swizzled IMP calls through correctly. */
  55. - (void)testOriginalImpCallThrough {
  56. SEL selector = @selector(description);
  57. Class aClass = [NSObject class];
  58. id newDescription = ^NSString *(id object) {
  59. IMP originalImp =
  60. [FIRSwizzler originalImplementationForClass:aClass selector:selector isClassSelector:NO];
  61. typedef NSString *(*OriginalImp)(id, SEL);
  62. NSString *originalDescription = ((OriginalImp)(originalImp))(object, selector);
  63. return [originalDescription stringByAppendingString:@"SWIZZLED!"];
  64. };
  65. [FIRSwizzler swizzleClass:aClass selector:selector isClassSelector:NO withBlock:newDescription];
  66. NSString *result = [[[NSObject alloc] init] description];
  67. XCTAssertGreaterThan([result rangeOfString:@"SWIZZLED!"].location, 0);
  68. [FIRSwizzler unswizzleClass:aClass selector:selector isClassSelector:NO];
  69. }
  70. /** Tests originalImplementationForClass:selector:isClassSelector: returns the original class IMP.
  71. */
  72. - (void)testOriginalImpClassMethod {
  73. Method method = class_getInstanceMethod([NSObject class], @selector(description));
  74. IMP originalImp = method_getImplementation(method);
  75. NSString * (^newImplementation)() = ^NSString *() {
  76. return @"nonsense";
  77. };
  78. [FIRSwizzler swizzleClass:[NSObject class]
  79. selector:@selector(description)
  80. isClassSelector:NO
  81. withBlock:newImplementation];
  82. IMP returnedImp = [FIRSwizzler originalImplementationForClass:[NSObject class]
  83. selector:@selector(description)
  84. isClassSelector:NO];
  85. XCTAssertEqual(returnedImp, originalImp);
  86. [FIRSwizzler unswizzleClass:[NSObject class] selector:@selector(description) isClassSelector:NO];
  87. }
  88. /** Tests originalImplementationForClass:selector:isClassSelector: returns different IMPs for
  89. * instance methods and class methods of the same name (like -/+ description).
  90. */
  91. - (void)testOriginalImpInstanceAndClassImpsAreDifferent {
  92. Method instanceMethod = class_getInstanceMethod([NSObject class], @selector(description));
  93. Method classMethod = class_getClassMethod([NSObject class], @selector(description));
  94. IMP instanceImp = method_getImplementation(instanceMethod);
  95. IMP classImp = method_getImplementation(classMethod);
  96. NSString * (^newImplementation)() = ^NSString *() {
  97. return @"nonsense";
  98. };
  99. [FIRSwizzler swizzleClass:[NSObject class]
  100. selector:@selector(description)
  101. isClassSelector:NO
  102. withBlock:newImplementation];
  103. [FIRSwizzler swizzleClass:[NSObject class]
  104. selector:@selector(description)
  105. isClassSelector:YES
  106. withBlock:newImplementation];
  107. XCTAssertNotEqual(instanceMethod, classMethod);
  108. IMP returnedInstanceImp = [FIRSwizzler originalImplementationForClass:[NSObject class]
  109. selector:@selector(description)
  110. isClassSelector:NO];
  111. IMP returnedClassImp = [FIRSwizzler originalImplementationForClass:[NSObject class]
  112. selector:@selector(description)
  113. isClassSelector:YES];
  114. XCTAssertNotEqual(instanceImp, classImp);
  115. XCTAssertNotEqual(returnedInstanceImp, returnedClassImp);
  116. [FIRSwizzler unswizzleClass:[NSObject class] selector:@selector(description) isClassSelector:NO];
  117. [FIRSwizzler unswizzleClass:[NSObject class] selector:@selector(description) isClassSelector:YES];
  118. }
  119. /** Tests swizzling an instance method. */
  120. - (void)testSwizzleInstanceMethod {
  121. NSString *expectedDescription = @"Not what you expected!";
  122. NSString * (^newImplementation)() = ^NSString *() {
  123. return expectedDescription;
  124. };
  125. [FIRSwizzler swizzleClass:[NSObject class]
  126. selector:@selector(description)
  127. isClassSelector:NO
  128. withBlock:newImplementation];
  129. NSString *returnedDescription = [[[NSObject alloc] init] description];
  130. XCTAssertEqualObjects(returnedDescription, expectedDescription);
  131. [FIRSwizzler unswizzleClass:[NSObject class] selector:@selector(description) isClassSelector:NO];
  132. }
  133. /** Tests swizzling a class method. */
  134. - (void)testSwizzleClassMethod {
  135. NSString *expectedDescription = @"Swizzled class description";
  136. NSString * (^newImplementation)() = ^NSString *() {
  137. return expectedDescription;
  138. };
  139. [FIRSwizzler swizzleClass:[NSObject class]
  140. selector:@selector(description)
  141. isClassSelector:YES
  142. withBlock:newImplementation];
  143. XCTAssertEqualObjects([NSObject description], expectedDescription);
  144. [FIRSwizzler unswizzleClass:[NSObject class] selector:@selector(description) isClassSelector:YES];
  145. }
  146. /** Tests unswizzling an instance method. */
  147. - (void)testUnswizzleInstanceMethod {
  148. NSObject *object = [[NSObject alloc] init];
  149. NSString *originalDescription = [object description];
  150. NSString *swizzledDescription = @"Swizzled description";
  151. NSString * (^newImplementation)() = ^NSString *() {
  152. return swizzledDescription;
  153. };
  154. [FIRSwizzler swizzleClass:[NSObject class]
  155. selector:@selector(description)
  156. isClassSelector:NO
  157. withBlock:newImplementation];
  158. NSString *returnedDescription = [object description];
  159. XCTAssertEqualObjects(returnedDescription, swizzledDescription);
  160. [FIRSwizzler unswizzleClass:[NSObject class] selector:@selector(description) isClassSelector:NO];
  161. returnedDescription = [object description];
  162. XCTAssertEqualObjects(returnedDescription, originalDescription);
  163. }
  164. /** Tests unswizzling a class method. */
  165. - (void)testUnswizzleClassMethod {
  166. NSString *originalDescription = [NSObject description];
  167. NSString *swizzledDescription = @"Swizzled class description";
  168. NSString * (^newImplementation)() = ^NSString *() {
  169. return swizzledDescription;
  170. };
  171. [FIRSwizzler swizzleClass:[NSObject class]
  172. selector:@selector(description)
  173. isClassSelector:YES
  174. withBlock:newImplementation];
  175. XCTAssertEqualObjects([NSObject description], swizzledDescription);
  176. [FIRSwizzler unswizzleClass:[NSObject class] selector:@selector(description) isClassSelector:YES];
  177. XCTAssertEqualObjects([NSObject description], originalDescription);
  178. }
  179. /** Tests swizzling a class method doesn't swizzle an instance method of the same name. */
  180. - (void)testSwizzlingAClassMethodDoesntSwizzleAnInstanceMethod {
  181. NSString *expectedDescription = @"Swizzled class description";
  182. NSString * (^newImplementation)() = ^NSString *() {
  183. return expectedDescription;
  184. };
  185. [FIRSwizzler swizzleClass:[NSObject class]
  186. selector:@selector(description)
  187. isClassSelector:YES
  188. withBlock:newImplementation];
  189. XCTAssertEqualObjects([NSObject description], expectedDescription);
  190. XCTAssertNotEqualObjects([[[NSObject alloc] init] description], expectedDescription);
  191. [FIRSwizzler unswizzleClass:[NSObject class] selector:@selector(description) isClassSelector:YES];
  192. }
  193. /** Tests swizzling an instance method doesn't swizzle a class method of the same name. */
  194. - (void)testSwizzlingAnInstanceMethodDoesntSwizzleAClassMethod {
  195. NSString *expectedDescription = @"Not what you expected!";
  196. NSString * (^newImplementation)() = ^NSString *() {
  197. return expectedDescription;
  198. };
  199. [FIRSwizzler swizzleClass:[NSObject class]
  200. selector:@selector(description)
  201. isClassSelector:NO
  202. withBlock:newImplementation];
  203. NSString *returnedDescription = [[[NSObject alloc] init] description];
  204. XCTAssertEqual(returnedDescription, expectedDescription);
  205. XCTAssertNotEqualObjects([NSObject description], expectedDescription);
  206. [FIRSwizzler unswizzleClass:[NSObject class] selector:@selector(description) isClassSelector:NO];
  207. }
  208. /** Tests swizzling a superclass's instance method. */
  209. - (void)testSwizzlingSuperclassInstanceMethod {
  210. NSObject *generalObject = [[NSObject alloc] init];
  211. BOOL generalObjectIsProxyValue = [generalObject isProxy];
  212. BOOL (^newImplementation)() = ^BOOL() {
  213. return !generalObjectIsProxyValue;
  214. };
  215. [FIRSwizzler swizzleClass:[TestObject class]
  216. selector:@selector(isProxy)
  217. isClassSelector:NO
  218. withBlock:newImplementation];
  219. XCTAssertNotEqual([[[TestObject alloc] init] isProxy], generalObjectIsProxyValue);
  220. [FIRSwizzler unswizzleClass:[TestObject class] selector:@selector(isProxy) isClassSelector:NO];
  221. }
  222. /** Tests swizzling a superclass's class method. */
  223. - (void)testSwizzlingSuperclassClassMethod {
  224. NSString *expectedDescription = @"Swizzled class description";
  225. NSString * (^newImplementation)() = ^NSString *() {
  226. return expectedDescription;
  227. };
  228. [FIRSwizzler swizzleClass:[TestObject class]
  229. selector:@selector(description)
  230. isClassSelector:YES
  231. withBlock:newImplementation];
  232. XCTAssertEqualObjects([TestObject description], expectedDescription);
  233. [FIRSwizzler unswizzleClass:[TestObject class]
  234. selector:@selector(description)
  235. isClassSelector:YES];
  236. }
  237. /** Tests swizzling an instance method that calls into the superclass implementation. */
  238. - (void)testSwizzlingInstanceMethodThatCallsSuper {
  239. NSString *expectedDescription = [[[TestObject alloc] init] description];
  240. NSString * (^newImplementation)() = ^NSString *() {
  241. return expectedDescription;
  242. };
  243. [FIRSwizzler swizzleClass:[TestObject class]
  244. selector:@selector(description)
  245. isClassSelector:NO
  246. withBlock:newImplementation];
  247. XCTAssertEqual([[[TestObject alloc] init] description], expectedDescription);
  248. [FIRSwizzler unswizzleClass:[TestObject class]
  249. selector:@selector(description)
  250. isClassSelector:NO];
  251. XCTAssertNotEqual([[[TestObject alloc] init] description], expectedDescription);
  252. }
  253. /** Tests swizzling a method and getting the original IMP of that method. */
  254. - (void)testSwizzleAndGet {
  255. Class testClass = [NSURL class];
  256. SEL testSelector = @selector(description);
  257. IMP baseImp = class_getMethodImplementation(testClass, testSelector);
  258. [FIRSwizzler swizzleClass:testClass
  259. selector:testSelector
  260. isClassSelector:NO
  261. withBlock:^{
  262. return @"Swizzled Description";
  263. }];
  264. IMP origImp = [FIRSwizzler originalImplementationForClass:testClass
  265. selector:testSelector
  266. isClassSelector:NO];
  267. XCTAssertEqual(origImp, baseImp, @"Original IMP and base IMP are not equal.");
  268. [FIRSwizzler unswizzleClass:testClass selector:testSelector isClassSelector:NO];
  269. }
  270. /** Tests swizzling more than a single method at a time. */
  271. - (void)testSwizzleMultiple {
  272. Class testClass = [NSURL class];
  273. SEL testSelector = @selector(description);
  274. [FIRSwizzler swizzleClass:testClass
  275. selector:testSelector
  276. isClassSelector:NO
  277. withBlock:^{
  278. return @"Swizzled Description";
  279. }];
  280. IMP origImp = [FIRSwizzler originalImplementationForClass:testClass
  281. selector:testSelector
  282. isClassSelector:NO];
  283. Class testClass2 = [NSURLRequest class];
  284. SEL testSelector2 = @selector(debugDescription);
  285. [FIRSwizzler swizzleClass:testClass2
  286. selector:testSelector2
  287. isClassSelector:NO
  288. withBlock:^{
  289. return @"Swizzled Debug Description";
  290. }];
  291. IMP origImp2 = [FIRSwizzler originalImplementationForClass:testClass2
  292. selector:testSelector2
  293. isClassSelector:NO];
  294. XCTAssertNotEqual(origImp2, NULL, @"Original IMP is NULL after swizzle.");
  295. XCTAssertNotEqual(origImp, origImp2, @"Implementations are the same when they should't be.");
  296. [FIRSwizzler unswizzleClass:testClass selector:testSelector isClassSelector:NO];
  297. [FIRSwizzler unswizzleClass:testClass2 selector:testSelector2 isClassSelector:NO];
  298. }
  299. /** Tests swizzling a class method that calls into the superclass implementation. */
  300. - (void)testSwizzlingClassMethodThatCallsSuper {
  301. NSString *expectedDescription = @"Swizzled class description";
  302. NSString * (^newImplementation)() = ^NSString *() {
  303. return expectedDescription;
  304. };
  305. [FIRSwizzler swizzleClass:[TestObject class]
  306. selector:@selector(description)
  307. isClassSelector:YES
  308. withBlock:newImplementation];
  309. XCTAssertEqualObjects([TestObject description], expectedDescription);
  310. [FIRSwizzler unswizzleClass:[TestObject class]
  311. selector:@selector(description)
  312. isClassSelector:YES];
  313. }
  314. /** Tests swizzling an inherited instance method doesn't change the implementation of the
  315. * superclass's implementation of that same method.
  316. */
  317. - (void)testSwizzlingAnInheritedInstanceMethodDoesntAffectTheIMPOfItsSuperclass {
  318. NSObject *generalObject = [[NSObject alloc] init];
  319. BOOL expectedGeneralObjectValue = [generalObject isProxy];
  320. BOOL (^newImplementation)() = ^BOOL() {
  321. return !expectedGeneralObjectValue;
  322. };
  323. [FIRSwizzler swizzleClass:[TestObject class]
  324. selector:@selector(isProxy)
  325. isClassSelector:NO
  326. withBlock:newImplementation];
  327. XCTAssertEqual([generalObject isProxy], expectedGeneralObjectValue);
  328. XCTAssertNotEqual([[[TestObject alloc] init] isProxy], expectedGeneralObjectValue);
  329. [FIRSwizzler unswizzleClass:[TestObject class] selector:@selector(isProxy) isClassSelector:NO];
  330. XCTAssertEqual([[[TestObject alloc] init] isProxy], expectedGeneralObjectValue);
  331. }
  332. /** Tests swizzling an inherited instance method from a superclass a couple of links up in the
  333. * chain of superclasses doesn't affect the implementation of the superclass's method.
  334. */
  335. - (void)testSwizzlingADeeperInheritedInstanceMethodDoesntAffectTheIMPOfItsSuperclass {
  336. TestObject *testObject = [[TestObject alloc] init];
  337. BOOL expectedTestObjectValue = [testObject isProxy];
  338. BOOL (^newImplementation)() = ^BOOL() {
  339. return !expectedTestObjectValue;
  340. };
  341. [FIRSwizzler swizzleClass:[TestObjectSubclass class]
  342. selector:@selector(isProxy)
  343. isClassSelector:NO
  344. withBlock:newImplementation];
  345. XCTAssertEqual([testObject isProxy], expectedTestObjectValue);
  346. XCTAssertNotEqual([[[TestObjectSubclass alloc] init] isProxy], expectedTestObjectValue);
  347. [FIRSwizzler unswizzleClass:[TestObjectSubclass class]
  348. selector:@selector(isProxy)
  349. isClassSelector:NO];
  350. XCTAssertEqual([[[TestObjectSubclass alloc] init] isProxy], expectedTestObjectValue);
  351. }
  352. /** Tests swizzling an inherited class method doesn't change the implementation of the
  353. * superclass's implementation of that same method.
  354. */
  355. - (void)testSwizzlingAnInheritedClassMethodDoesntAffectTheIMPOfItsSuperclass {
  356. // Fun fact, this won't work on +new. Swizzling +new causes a retain to not be placed correctly.
  357. NSString *expectedDescription = [TestObject description];
  358. NSString * (^newImplementation)() = ^NSString *() {
  359. return expectedDescription;
  360. };
  361. [FIRSwizzler swizzleClass:[TestObject class]
  362. selector:@selector(description)
  363. isClassSelector:YES
  364. withBlock:newImplementation];
  365. XCTAssertEqual([TestObject description], expectedDescription);
  366. XCTAssertNotEqual([NSObject description], expectedDescription);
  367. [FIRSwizzler unswizzleClass:[TestObject class]
  368. selector:@selector(description)
  369. isClassSelector:YES];
  370. XCTAssertNotEqual([TestObject description], expectedDescription);
  371. XCTAssertNotEqual([NSObject description], expectedDescription);
  372. }
  373. /** Tests swizzling an inherited class method from a superclass a couple of links up in the
  374. * chain of superclasses doesn't affect the implementation of the superclass's method.
  375. */
  376. - (void)testSwizzlingADeeperInheritedClassMethodDoesntAffectTheIMPOfItsSuperclass {
  377. NSString *expectedDescription = [TestObjectSubclass description];
  378. NSString * (^newImplementation)() = ^NSString *() {
  379. return expectedDescription;
  380. };
  381. [FIRSwizzler swizzleClass:[TestObjectSubclass class]
  382. selector:@selector(description)
  383. isClassSelector:YES
  384. withBlock:newImplementation];
  385. XCTAssertEqual([TestObjectSubclass description], expectedDescription);
  386. XCTAssertNotEqual([TestObject description], expectedDescription);
  387. XCTAssertNotEqual([NSObject description], expectedDescription);
  388. [FIRSwizzler unswizzleClass:[TestObjectSubclass class]
  389. selector:@selector(description)
  390. isClassSelector:YES];
  391. XCTAssertNotEqual([TestObjectSubclass description], expectedDescription);
  392. XCTAssertNotEqual([TestObject description], expectedDescription);
  393. XCTAssertNotEqual([NSObject description], expectedDescription);
  394. }
  395. @end