我正在玩弄使用 Core Data 来管理对象图,主要用于依赖注入(NSManagedObjects 的一个子集确实需要持久化,但这不是我问题的重点)。运行单元测试时,我想接管 NSManagedObjects 的创建,用模拟替换它们。
我现在确实有一种候选方法,即使用运行时的 method_exchangeImplementations[NSEntityDescription insertNewObjectForEntityForName:inManagedObjectContext:]
与我自己的实现进行交换(即返回模拟)。这适用于我所做的一个小测试。
我对此有两个问题:
- 有没有比 swizzling insertNewObjectForEntityForName:inManagedObjectContext 更好的方法来替换 Core Data 的对象创建?我还没有深入研究运行时或核心数据,可能会遗漏一些明显的东西。
- 我的替换对象创建方法概念是返回模拟的 NSManagedObjects。我正在使用 OCMock,它不会直接模拟 NSManagedObject 子类,因为它们是动态
@property
的。现在我的 NSManagedObject 的客户正在与协议而不是具体对象交谈,所以我返回模拟协议而不是具体对象。有没有更好的办法?
这是一些伪代码来说明我的意思。这是我可能正在测试的一个类:
@interface ClassUnderTest : NSObject
- (id) initWithAnObject:(Thingy *)anObject anotherObject:(Thingo *)anotherObject;
@end
@interface ClassUnderTest()
@property (strong, nonatomic, readonly) Thingy *myThingy;
@property (strong, nonatomic, readonly) Thingo *myThingo;
@end
@implementation ClassUnderTest
@synthesize myThingy = _myThingy, myThingo = _myThingo;
- (id) initWithAnObject:(Thingy *)anObject anotherObject:(Thingo *)anotherObject {
if((self = [super init])) {
_myThingy = anObject;
_myThingo = anotherObject;
}
return self;
}
@end
我决定制作 Thingy 和 Thingo NSManagedObject 子类,也许是为了持久性等,但也可以用类似的东西替换 init:
@interface ClassUnderTest : NSObject
- (id) initWithManageObjectContext:(NSManagedObjectContext *)context;
@end
@implementation ClassUnderTest
@synthesize myThingy = managedObjectContext= _managedObjectContext, _myThingy, myThingo = _myThingo;
- (id) initWithManageObjectContext:(NSManagedObjectContext *)context {
if((self = [super init])) {
_managedObjectContext = context;
_myThingy = [NSEntityDescription insertNewObjectForEntityForName:@"Thingy" inManagedObjectContext:context];
_myThingo = [NSEntityDescription insertNewObjectForEntityForName:@"Thingo" inManagedObjectContext:context];
}
return self;
}
@end
然后在我的单元测试中,我可以执行以下操作:
- (void)setUp {
Class entityDescrClass = [NSEntityDescription class];
Method originalMethod = class_getClassMethod(entityDescrClass, @selector(insertNewObjectForEntityForName:inManagedObjectContext:));
Method newMethod = class_getClassMethod([FakeEntityDescription class], @selector(insertNewObjectForEntityForName:inManagedObjectContext:));
method_exchangeImplementations(originalMethod, newMethod);
}
...我的[]FakeEntityDescription insertNewObjectForEntityForName:inManagedObjectContext]
返回模拟代替了真正的 NSManagedObjects(或它们实现的协议)。这些模拟的唯一目的是在对 ClassUnderTest 进行单元测试时验证对它们的调用。所有返回值都将被存根(包括任何引用其他 NSManagedObjects 的 getter)。
我的测试ClassUnderTest
实例将在单元测试中创建,因此:
ClassUnderTest *testObject = [ClassUnderTest initWithManagedObjectContext:mockContext];
(上下文实际上不会在测试中使用,因为我的 swizzled insertNewObjectForEntityForName:inManagedObjectContext
)
这一切的重点?无论如何,我将在许多类中使用 Core Data,所以我不妨使用它来帮助减轻管理构造函数更改的负担(每个构造函数更改都涉及编辑所有客户端,包括一堆单元测试)。如果我没有使用 Core Data,我可能会考虑使用Objection 之类的东西。