13
13
// limitations under the License.
14
14
15
15
#import < XCTest/XCTest.h>
16
+ #import < objc/runtime.h>
16
17
17
18
#import < GoogleUtilities/GULObjectSwizzler.h>
19
+ #import < GoogleUtilities/GULProxy.h>
18
20
#import < GoogleUtilities/GULSwizzledObject.h>
19
21
20
22
@interface GULObjectSwizzlerTest : XCTestCase
@@ -23,6 +25,16 @@ @interface GULObjectSwizzlerTest : XCTestCase
23
25
24
26
@implementation GULObjectSwizzlerTest
25
27
28
+ /* * Used as a donor method to add a method that doesn't exist on the superclass. */
29
+ - (NSString *)donorDescription {
30
+ return @" SwizzledDonorDescription" ;
31
+ }
32
+
33
+ /* * Used as a donor method to add a method that exists on the superclass. */
34
+ - (NSString *)description {
35
+ return @" SwizzledDescription" ;
36
+ }
37
+
26
38
/* * Exists just as a donor method. */
27
39
- (void )donorMethod {
28
40
}
@@ -226,6 +238,7 @@ - (void)testSetGetAssociatedObjectRetainNonatomic {
226
238
XCTAssertEqualObjects (returnedObject, associatedObject);
227
239
}
228
240
241
+ /* * Tests getting and setting an associated object with an invalid association type. */
229
242
- (void )testSetGetAssociatedObjectWithoutProperAssociation {
230
243
NSObject *object = [[NSObject alloc ] init ];
231
244
NSDictionary *associatedObject = [[NSDictionary alloc ] init ];
@@ -235,4 +248,105 @@ - (void)testSetGetAssociatedObjectWithoutProperAssociation {
235
248
XCTAssertEqualObjects (returnedObject, associatedObject);
236
249
}
237
250
251
+ /* * Tests using the GULObjectSwizzler to swizzle an object wrapped in an NSProxy. */
252
+ - (void )testSwizzleProxiedObject {
253
+ NSObject *object = [[NSObject alloc ] init ];
254
+ GULProxy *proxyObject = [GULProxy proxyWithDelegate: object];
255
+ GULObjectSwizzler *swizzler = [[GULObjectSwizzler alloc ] initWithObject: proxyObject];
256
+
257
+ XCTAssertNoThrow ([swizzler swizzle ]);
258
+
259
+ XCTAssertNotEqual (object_getClass (proxyObject), [GULProxy class ]);
260
+ XCTAssertTrue ([object_getClass (proxyObject) isSubclassOfClass: [GULProxy class ]]);
261
+
262
+ XCTAssertTrue ([proxyObject respondsToSelector: @selector (gul_objectSwizzler )]);
263
+ XCTAssertNoThrow ([proxyObject performSelector: @selector (gul_objectSwizzler )]);
264
+
265
+ XCTAssertTrue ([proxyObject respondsToSelector: @selector (gul_class )]);
266
+ XCTAssertNoThrow ([proxyObject performSelector: @selector (gul_class )]);
267
+ }
268
+
269
+ /* * Tests overriding a method that already exists on a proxied object works as expected. */
270
+ - (void )testSwizzleProxiedObjectInvokesInjectedMethodWhenOverridingMethod {
271
+ NSObject *object = [[NSObject alloc ] init ];
272
+ GULProxy *proxyObject = [GULProxy proxyWithDelegate: object];
273
+
274
+ GULObjectSwizzler *swizzler = [[GULObjectSwizzler alloc ] initWithObject: proxyObject];
275
+ [swizzler copySelector: @selector (description )
276
+ fromClass: [GULObjectSwizzlerTest class ]
277
+ isClassSelector: NO ];
278
+ [swizzler swizzle ];
279
+
280
+ XCTAssertEqual ([proxyObject performSelector: @selector (description )], @" SwizzledDescription" );
281
+ }
282
+
283
+ /* * Tests adding a method that doesn't exist on a proxied object works as expected. */
284
+ - (void )testSwizzleProxiedObjectInvokesInjectedMethodWhenAddingMethod {
285
+ NSObject *object = [[NSObject alloc ] init ];
286
+ GULProxy *proxyObject = [GULProxy proxyWithDelegate: object];
287
+
288
+ GULObjectSwizzler *swizzler = [[GULObjectSwizzler alloc ] initWithObject: proxyObject];
289
+ [swizzler copySelector: @selector (donorDescription )
290
+ fromClass: [GULObjectSwizzlerTest class ]
291
+ isClassSelector: NO ];
292
+ [swizzler swizzle ];
293
+
294
+ XCTAssertEqual ([proxyObject performSelector: @selector (donorDescription )],
295
+ @" SwizzledDonorDescription" );
296
+ }
297
+
298
+ /* * Tests KVOing a proxy object that we've ISA Swizzled works as expected. */
299
+ - (void )testRespondsToSelectorWorksEvenIfSwizzledProxyIsKVOd {
300
+ NSObject *object = [[NSObject alloc ] init ];
301
+ GULProxy *proxyObject = [GULProxy proxyWithDelegate: object];
302
+
303
+ GULObjectSwizzler *swizzler = [[GULObjectSwizzler alloc ] initWithObject: proxyObject];
304
+ [swizzler copySelector: @selector (donorDescription )
305
+ fromClass: [GULObjectSwizzlerTest class ]
306
+ isClassSelector: NO ];
307
+ [swizzler swizzle ];
308
+
309
+ [(NSObject *)proxyObject addObserver: self
310
+ forKeyPath: NSStringFromSelector (@selector (description ))
311
+ options: 0
312
+ context: NULL ];
313
+
314
+ XCTAssertTrue ([proxyObject respondsToSelector: @selector (donorDescription )]);
315
+ XCTAssertEqual ([proxyObject performSelector: @selector (donorDescription )],
316
+ @" SwizzledDonorDescription" );
317
+
318
+ [(NSObject *)proxyObject removeObserver: self
319
+ forKeyPath: NSStringFromSelector (@selector (description ))];
320
+ }
321
+
322
+ /* * Tests that -[NSObjectProtocol resopondsToSelector:] works as expected after someone else ISA
323
+ * swizzles a proxy object that we've also ISA Swizzled.
324
+ */
325
+ - (void )testRespondsToSelectorWorksEvenIfSwizzledProxyISASwizzledBySomeoneElse {
326
+ NSObject *object = [[NSObject alloc ] init ];
327
+ GULProxy *proxyObject = [GULProxy proxyWithDelegate: object];
328
+
329
+ GULObjectSwizzler *swizzler = [[GULObjectSwizzler alloc ] initWithObject: proxyObject];
330
+ [swizzler copySelector: @selector (donorDescription )
331
+ fromClass: [GULObjectSwizzlerTest class ]
332
+ isClassSelector: NO ];
333
+ [swizzler swizzle ];
334
+
335
+ // Someone else ISA Swizzles the same object after GULObjectSwizzler.
336
+ Class originalClass = object_getClass (proxyObject);
337
+ NSString *newClassName =
338
+ [NSString stringWithFormat: @" gul_test_%p _%@ " , proxyObject, NSStringFromClass (originalClass)];
339
+ Class generatedClass = objc_allocateClassPair (originalClass, newClassName.UTF8String , 0 );
340
+ objc_registerClassPair (generatedClass);
341
+ object_setClass (proxyObject, generatedClass);
342
+
343
+ XCTAssertTrue ([proxyObject respondsToSelector: @selector (donorDescription )]);
344
+ XCTAssertEqual ([proxyObject performSelector: @selector (donorDescription )],
345
+ @" SwizzledDonorDescription" );
346
+
347
+ // Clean up.
348
+ object_setClass (proxyObject, originalClass);
349
+ objc_disposeClassPair (generatedClass);
350
+ }
351
+
238
352
@end
0 commit comments