使用从测试方法调用的方法的类的OCMock

时间:2011-09-17 18:17:42

标签: objective-c ios unit-testing ocunit ocmock

我正在尝试测试实例化MFMailComposeViewController实例的方法。正在测试的方法调用MFMailComposeViewController的几种方法,包括setSubject:

我想测试setSubject是否发送了一个特定的NSString,在本例中是@“Test Message”。
无论我为模拟存根中的预期字符串指定什么,都没有失败。

在单元测试课程中:

#import <OCMock/OCMock.h>

- (void)testEmail {
    TestClass *testInstance = [[TestClass alloc] init];

    id mock = [OCMockObject mockForClass:[MFMailComposeViewController class]];
    [[mock stub] setSubject:@"Test Message"];

    [testInstance testMethod];
}

在TestClass中:

- (void)testMethod {
    MFMailComposeViewController *mailComposeVC = [[MFMailComposeViewController alloc] init];
    [mailComposeVC setSubject:@"Bad Message"];
}

Test Suite 'Email_Tests' started at 2011-09-17 18:12:21 +0000
Test Case '-[Email_Tests testEmail]' started.
Test Case '-[Email_Tests testEmail]' passed (0.041 seconds).

测试应该失败。

我正在iOS模拟器中对此进行测试,并在设备上获得相同的结果。

我做错了什么?有没有办法实现这个目标?

2 个答案:

答案 0 :(得分:5)

您创建了一个模拟,但从未将其传递给测试中的类,或者让模拟器验证自己。你需要某种形式的依赖注入来说,“而不是使用MFMailComposeViewController,使用我给你的另一件事。”

这是一种方法。在被测试的类中,不是直接分配MFMailComposeViewController,而是通过工厂方法获取它,如下所示:

@interface TestClass : NSObject

- (void)testMethod;

// Factory methods
+ (id)mailComposeViewController;

@end

这是实施。您正在泄漏,请注意工厂方法返回一个自动释放的对象。

- (void)testMethod {
    MFMailComposeViewController *mailComposeVC =
                                    [[self class] mailComposeViewController];
    [mailComposeVC setSubject:@"Bad Message"];
}

+ (id)mailComposeViewController {
    return [[[MFMailComposeViewController alloc] init] autorelease];
}

在测试方面,我们创建一个覆盖工厂方法的测试子类,以便它提供我们想要的任何内容:

@interface TestingTestClass : TestClass
@property(nonatomic, assign) id mockMailComposeViewController;
@end

@implementation TestingTestClass
@synthesize mockMailComposeViewController;

+ (id)mailComposeViewController {
    return mockMailComposeViewController;
}

@end

现在我们已准备好进行测试了。我做了一些不同的事情:

  • 分配测试子类而不是实际的类(并且不要泄漏!)
  • 设置具有期望的模拟,而不仅仅是存根
  • 将模拟注入测试子类
  • 最后验证模拟

以下是测试:

- (void) testEmail {
    TestClass *testInstance = [[[TestClass alloc] init] autorelease];

    id mock = [OCMockObject mockForClass:[MFMailComposeViewController class]];
    [[mock expect] setSubject:@"Test Message"];
    [testInstance setMockMailComposeViewController:mock];

    [testInstance testMethod];

    [mock verify];
}

为了完整性,我们需要一个最终测试,这是为了保证实际类中的工厂方法返回我们期望的结果:

- (void)testMailComposerViewControllerShouldBeCorrectType {
    STAssertTrue([[TestClass mailComposeViewController]
                 isKindOfClass:[MFMailComposeViewController class]], nil);
}

答案 1 :(得分:2)

Jon Reid是一种合理的方法,虽然看起来让mailComposeViewController类方法变得复杂。并在测试代码中对其进行子类化意味着您将始终在测试时获得模拟版本,这可能不是您想要的。我会把它变成一个实例方法。然后,您可以使用部分模拟在测试时覆盖它:

-(void) testEmail {
    TestClass *testInstance = [[[TestClass alloc] init] autorelease];

    id mock = [OCMockObject mockForClass:[MFMailComposeViewController class]];
    [[mock expect] setSubject:@"Test Message"];
    id mockInstance = [OCMockObject partialMockForObject:testInstance];
    [[[mockInstance stub] andReturn:mock] mailComposeViewController];

    [testInstance testMethod];

    [mock verify];
}

如果将它保留为类方法,您可以考虑将其设置为静态全局并公开覆盖它的方法:

static MFMailComposeViewController *mailComposeViewController = nil;

-(id)mailComposeViewController {
    if (!mailComposeViewController) {
        mailComposeViewController = [[MFMailComposeViewController alloc] init];
    }
    return mailComposeViewController;
}

-(void)setMailComposeViewController:(MFMailComposeViewController *)controller {
    mailComposeViewController = controller;
}

然后,你的测试将类似于Jon的例子:

-(void)testEmail {
    TestClass *testInstance = [[[TestClass alloc] init] autorelease];

    id mock = [OCMockObject mockForClass:[MFMailComposeViewController class]];
    [[mock expect] setSubject:@"Test Message"];
    [testInstance setMailComposeViewController:mock];

    [testInstance testMethod];

    [mock verify];

    // clean up
    [testInstance setMailComposeViewController:nil];
}