如何在Objective-C中对方法内部的对象进行单元测试?

时间:2009-07-06 20:56:58

标签: objective-c unit-testing mocking ocmock

我想知道如何测试这个。我有一个方法,它接受一个参数,并根据该参数的一些属性创建另一个对象并对其进行操作。代码看起来像这样:

- (void) navigate:(NavContext *)context {
  Destination * dest = [[Destination alloc] initWithContext:context];
  if (context.isValid) {
    [dest doSomething];
  } else {
    // something else
  }
  [dest release];
}

我要验证的是,如果context.isValid为true,那么在dest上调用doSomething,但我不知道如何使用OCMock或任何其他传统测试方法测试它(或者甚至可能)该对象完全在方法范围内创建。我这是错误的方式吗?

4 个答案:

答案 0 :(得分:5)

您可以使用OCMock,但是您必须修改代码以获取Destination对象或使用可以首先替换为模拟对象的单例对象。

最简洁的方法可能是实现

-(void) navigate:(NavContext *)context destination:(Destination *)dest;

方法。将-(void) navigate:(NavContext *)context的实施更改为以下内容:

- (void) navigate:(NavContext *)context {
    Destination * dest = [[Destination alloc] initWithContext:context];
    [self navigate:context destination:dest];
    [dest release];
}

这将允许您的测试直接使用额外参数调用该方法。 (在其他语言中,您只需为destination参数提供默认值即可实现此目的,但Objective-C不支持默认参数。)

答案 1 :(得分:1)

  

我要验证的是,如果context.isValid为true,则在dest上调用doSomething

我认为你可能在这里测试错误的东西。您可以安全地假设(我希望)布尔语句在ObjC中正常工作。您不想要测试Context对象吗?如果context.isValid,则保证[dest doSomething]分支被执行。

答案 2 :(得分:0)

使用像method swizzling这样有趣的技术是完全可能的,但它可能以错误的方式进行。如果完全没有办法观察从单元测试中调用doSomething的效果,那么它是否会调用doSomething实现细节?

(如果您要进行此测试,实现目标的一种方法是将doSomething Destination方法替换为通知您的单元测试的方法,然后将调用传递给{{ 1}}。)

答案 3 :(得分:0)

我喜欢在这种情况下使用工厂方法。

@interface Destination(Factory)
+ (Destination *)destinationWithContext:(NavContext *)context;
@end

@implementation Destination(Factory)
+ (Destination *)destinationWithContext:(NavContext *)context
{
    return [[Destination alloc] initWithContext:context];
}
@end

然后我做了一个FakeClass:

#import "Destination+Factory.h"

@interface FakeDestination : Destination
+ (id)sharedInstance;
+ (void)setSharedInstance:(id)sharedInstance;
// Note! Instance method!
- (Destination *)destinationWithContext:(NavContext *)context;
@end

@implementation FakeDestination
+ (id)sharedInstance
{
    static id _sharedInstance = nil;
    if (!_sharedInstance)
    {
        _sharedInstance = [[FakeDestination alloc] init];
    }
    return _sharedInstance;
}
+ (void)setSharedInstance:(id)sharedInstance
{
    _sharedInstance = sharedInstance;
}
// Overrides
+ (Destination *)destinationWithContext:(NavContext *)context { [FakeDestination.sharedInstance destinationWithContext:context]; }
// Instance
- (Destination *)destinationWithContext:(NavContext *)context { return nil; }
@end

完成此设置后,您只需+ (Destination *)destinationWithContext:(NavContext *)context;

swizzle the class methods

现在你已经设置为:

id destinationMock = [OCMock mockForClass:FakeDestination.class];
// do the swizzle
[FakeDestination setSharedInstance:destinationMock];
[[destinationMock expect] doSomething];
// Call your method
[destinationMock verify];

这是预先编写的大量编码,但它非常可重复使用。