如何使用 ocmock 验证部分模拟是不是具有使用 args 调用的基本方法?

Posted

技术标签:

【中文标题】如何使用 ocmock 验证部分模拟是不是具有使用 args 调用的基本方法?【英文标题】:How to verify a partial mock has a base method invoked with args using ocmock?如何使用 ocmock 验证部分模拟是否具有使用 args 调用的基本方法? 【发布时间】:2012-03-20 11:32:31 【问题描述】:

我正在使用一个非常简单的 Web 服务,它使用基类来重用一些常用功能。被测试的 main 方法只是简单地构建一个 url,然后它使用带有这个参数的 super / base 方法。

- (void)getPlacesForLocation:(Location *)location WithKeyword:(NSString *)keyword

    NSString *gps = [NSString stringWithFormat:@"?location=%@,%@", location.lat, location.lng];
    NSURL *url = [[NSURL alloc] initWithString:[NSString stringWithFormat:@"%@%@", self.baseurl, gps]];
    [super makeGetRequestWithURL:url];

这是基本方法定义

@implementation WebService
@synthesize responseData = _responseData;

- (id)init

    if (self == [super init])
    
        self.responseData = [NSMutableData new];        
    

    return self;


- (void)makeGetRequestWithURL:(NSURL *)url

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
    request.HTTPMethod = @"GET";

    [[NSURLConnection alloc] initWithRequest:request delegate:self];

在我的测试中,我创建了一个部分模拟,因为我仍然想调用我的测试对象,但我需要能够验证超级方法是以特定方式调用的。

- (void)testGetRequestMadeWithUrl

    self.sut = [[SomeWebService alloc] init];
    Location *location = [[Location alloc] initWithLatitude:@"-33.8670522" AndLongitude:@"151.1957362"];
    NSURL *url = [[NSURL alloc] initWithString:[NSString stringWithFormat:@"%@%@", self.sut.baseurl, @"?location=-33.8670522,151.1957362"]];
    id mockWebService = [OCMockObject partialMockForObject: self.sut];
    [[mockWebService expect] makeGetRequestWithURL:url];
    [self.sut getPlacesForLocation:location WithKeyword:@"foo"];
    [mockWebService verify];

但是,当我运行此测试时,我失败并出现以下错误:

没有调用预期的方法:makeGetRequestWithURL:https://...

我可以说这个方法没有被模拟,因为如果我将 NSLog 放入基本方法中,它会在我运行 ocunit 测试时显示(显然它正在运行,只是没有按照我的意愿模拟它)。

如何修改我的测试/重构我的实现代码以获得我正在寻找的断言?

【问题讨论】:

【参考方案1】:

这是一个有趣的案例。我的假设是,如果您将“super”替换为“self”,那么一切都会按预期进行,即。

- (void)getPlacesForLocation:(Location *)location WithKeyword:(NSString *)keyword

    NSString *gps = [NSString stringWithFormat:@"?location=%@,%@", location.lat, location.lng];
    NSURL *url = [[NSURL alloc] initWithString:[NSString stringWithFormat:@"%@%@", self.baseurl, gps]];
    [self makeGetRequestWithURL:url];

问题是部分模拟是通过动态创建子类来实现的。当使用“super”时,方法的查找从父类开始,在你的例子中是基类,这意味着运行时永远不会看到在部分模拟创建的子类中实现的方法。

您的问题的另一个答案是更改设计。不要使用类层次结构,而是使用两个类。一个类负责创建 URL,另一个类负责发出请求。然后你就不需要部分模拟了,因为你可以简单地替换请求者。参见单一职责原则 [1] 和组合优于继承 [2]。

[1]http://en.wikipedia.org/wiki/Single_responsibility_principle

[2]http://en.wikipedia.org/wiki/Composition_over_inheritance

【讨论】:

太棒了!感谢您的快速回复!我在这里使用 self 而不是 SUPER 有什么问题吗? 想不出。事实上,这很可能是你想要的。如果您在包含 getPlacesForLocation:WithKeyword: 方法的类中为 makeGetRequestWithURL: 提供了覆盖,您的期望是什么?您可能希望调用覆盖,对吗?通过使用 super 你是说你不希望这种情况发生并且你肯定想要来自父类的 impl。

以上是关于如何使用 ocmock 验证部分模拟是不是具有使用 args 调用的基本方法?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 OCMock 验证静态方法

使用具有未知数量的方法调用的 OCMock

类的部分模拟

OCMock 3 Partial Mock:类方法和 objc 运行时

在 NSMutableAttributedString 上模拟“initWithAttributedString”的 OCMock 失败

将 OCMock 与 Facebook 一起使用