测试已测试 | ✓ |
Lang语言 | Obj-CObjective C |
许可证 | MIT |
发布上次发布 | 2014年12月 |
由Gustavo Barbosa,Daniel L. Alves维护。
Redefine使得实现方法交换(在运行时使用objc运行时覆盖方法的实现)变得容易。它还使得在原始和新的实现之间进行切换变得可能。ALDRedefinition
使用了C++的RAII概念,因此用户只需确保维护对重定义对象的引用,以使其生效。当它被释放时,一切恢复正常。
它的明显用途是单元测试。不需要使用工厂、接口等来专门为测试准备代码,因为可以重新定义任何类或实例方法。当然,如果愿意的话,还可以做很多疯狂的事情 =D
主要功能包括
1.1.0版本的新特性
增加了从重定义的实现中调用原始实现的可能性!有关每个版本的信息,请参阅变更日志。
注意:Apple文档表示新的实现必须具有签名id^(id object, SEL selector, method_args...)
,这是错误的。您应遵循Redefine文档中指定的签名:id^(id object, method_args...)
1) 重新定义一个类方法
假设您想测试对于指定的用户而出现的行为,该用户由UserManager
管理
-( void )test_Greetings
{
ALDRedefinition *redefinition = [ALDRedefinition redefineClass: [UserManager class]
selector: @selector( currentUsername )
withImplementation: ^id(id object, ...) {
return @"John Doe";
}];
XCTAssertEqualObjects( [UserManager greetings], @"Hello, John Doe!" )
}
// ...
@implementation UserManager
// ...
+( NSString* )greetings
{
return [NSString stringWithFormat: @"Hello, %@!", [self currentUsername]];
}
// ...
@end
不必担心把currentUsername
的原始实现设置回原样,因为它将在ALDRedefinition
释放时自动完成。
2) 重新定义一个实例方法
假设您想测试只有在将值设置为您的NSUserDefaults
时才会发生的行为
-( void )test_When_Value_Is_Set_On_Standard_Defaults
{
ALDRedefinition *redefinition = [ALDRedefinition redefineClassInstances: [NSUserDefaults class]
selector: @selector( objectForKey: )
withImplementation: ^id(id object, ...) {
return @"Value";
}];
NSString* valueIWantToTest = [[NSUserDefaults standardUserDefaults] objectForKey: kMyAwsomeKey];
XCTAssertEqualObjects( valueIWantToTest, @"Value" );
}
如前所述,不必担心把objectForKey:
的原始实现设置回原样,因为它将在ALDRedefinition
释放时自动完成。
redefineClassInstances:selector:withImplementation:
是复数的,因为当重定义在位时,NSUserDefaults
类的所有实例都将重新定义其objectForKey:
。
注意类簇如NSArray
。
下面的代码将无法正常工作,因为 NSArray
是一个类簇,因此会返回其他覆盖了 objectForIndex:
方法的类
-( void )test_backward
{
// testArray is not really a NSArray
NSArray *testArray = @[ @1, @2, @3 ];
// ERROR! THIS WILL NOT WORK AS EXPECTED!!!
ALDRedefinition *redefinition = [ALDRedefinition redefineClassInstances: [NSArray class]
selector: @selector( objectAtIndex: )
withImplementation: ^id(id object, ...) {
return @"Mock";
}];
for( NSUInteger i = 0 ; i < testArray.count ; i++ )
XCTAssertEqualObjects( @"Mock", [testArray objectAtIndex: i] );
}
为了让它工作,我们需要使用 testArray
的实际类。因此,正确的代码是
-( void )test_backward
{
// testArray is not really a NSArray
NSArray *testArray = @[ @1, @2, @3 ];
// Ah-ha! Now everything is fine =)
ALDRedefinition *redefinition = [ALDRedefinition redefineClassInstances: [testArray class]
selector: @selector( objectAtIndex: )
withImplementation: ^id(id object, ...) {
return @"Mock";
}];
for( NSUInteger i = 0 ; i < testArray.count ; i++ )
XCTAssertEqualObjects( @"Mock", [testArray objectAtIndex: i] );
}
4) 随意重新定义并检查重新定义是否在位
当然,您不需要释放重新定义的对象使其失效
ALDRedefinition *redefinition = [ALDRedefinition redefineClassInstances: [NSArray class]
selector: @selector( firstObject )
withImplementation: ^id(id object, ...) {
return testArray.lastObject;
}];
// From now on, firstObject will return lastObject
// ...
// Let's bring the original implementation back
[redefinition stopUsingRedefinition];
// From now on, firstObject will return firstObject
// ...
// Nah, let's redefine it again
[redefinition startUsingRedefinition];
// ...
// Checks if a redefinition is in place
BOOL isRedefinitionInPlace = redefinition.usingRedefinition
5) 对同一个目标的多次重新定义
自 1.0.2 版本起,您可以设置同一个目标的多个重新定义。之前的重新定义将被停止。如果您想监听这些更改,现在 usingRedefinition
属性是 KVO 兼容的
NSString *test = @"original value";
// Creates a redefinition for NSString description
ALDRedefinition *firstRedefinition = [ALDRedefinition redefineClassInstances: [NSString class]
selector: @selector( description )
withImplementation: ^id(id object, ...) {
return @"first";
}];
// First redefinition is in place
assert( [[test description] isEqualToString: @"first"] );
// Creates another redefinition for NSString description
ALDRedefinition *secondRedefinition = [ALDRedefinition redefineClassInstances: [NSString class]
selector: @selector( description )
withImplementation: ^id(id object, ...) {
return @"second";
}];
// Second redefinition is in place...
assert( [[test description] isEqualToString: @"second"] );
// ... And the first redefinition has been stopped!
assert( firstRedefinition.usingRedefinition == NO );
// When we set firstRedefinition back...
[firstRedefinition startUsingRedefinition];
// ... The second redefinition is out!
assert( secondRedefinition.usingRedefinition == NO );
// Stopping the current redefinition...
[firstRedefinition stopUsingRedefinition];
// Brings the original implementation back
assert( [[test description] isEqualToString: @"original value"] );
// Hence, no redefinition is in use
assert( firstRedefinition.usingRedefinition == NO );
assert( secondRedefinition.usingRedefinition == NO );
6) 从重新定义的实现调用原始选择器实现
这自 1.1.0 版本以来是可能的
NSArray *testArray = @[ @0, @1, @2 ];
ALDRedefinition *redefinition = [ALDRedefinition redefineClassInstances: [testArray class]
selector: @selector( objectAtIndex: )
withPolymorphicImplementation: ^id( SEL selectorBeingRedefined, IMP originalImplementation ) {
return ^id( id object, ... ) {
va_list args;
va_start( args, object );
NSUInteger index = va_arg( args, NSUInteger );
va_end( args );
id original = originalImplementation( object, selectorBeingRedefined, index );
return [NSString stringWithFormat: @"Mock%@", original];
};
}];
assert( [[testArray objectAtIndex: 2] isEqualToString: @"Mock2"] );
Redefine 通过 CocoaPods 提供,要安装它,只需将以下行添加到您的 Podfile 中
pod "Redefine"
iOS 6.0 或更高版本,OSX 10.7 或更高版本,仅限 ARC
Redefine 可在 MIT 许可下使用。有关更多信息,请参阅 LICENSE 文件。