具有从测试方法调用的方法的类的 OCMock

发布于 2024-12-04 20:35:01 字数 1015 浏览 0 评论 0原文

我正在尝试测试一种实例化 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 模拟器中对此进行测试,并在设备上得到相同的结果。

我做错了什么?有什么办法可以做到这一点吗?

I am trying to test a method that instantiates an instance of MFMailComposeViewController. The method being tested calls several methods of MFMailComposeViewController including setSubject:.

I want to test that setSubject is sent a specific NSString, in this case @"Test Message".

No matter what I specify for the expected string in the mock stub there is no failure.

In the Unit Test class:

#import <OCMock/OCMock.h>

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

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

    [testInstance testMethod];
}

In 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).

The test should have failed.

I am testing this in the iOS simulator and get the same result on a device.

What am I doing wrong? Is there some way to accomplish this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

新一帅帅 2024-12-11 20:35:01

您创建了一个模拟,但从未将其传递给被测试的类,或要求模拟验证自身。您需要某种形式的依赖注入来表示:“不要使用 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);
}

You create a mock, but never pass it to the class under test, or ask the mock to verify itself. You need some form of dependency injection to say, "Instead of using MFMailComposeViewController, use this other thing I'm giving you."

Here's one way to do that. In the class under test, instead of allocating MFMailComposeViewController directly, get it through a factory method, like this:

@interface TestClass : NSObject

- (void)testMethod;

// Factory methods
+ (id)mailComposeViewController;

@end

Here's the implementation. You were leaking, so note that the factory method returns an autoreleased object.

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

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

Over on the testing side, we create a testing subclass that overrides the factory method so it provides whatever we want it to:

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

@implementation TestingTestClass
@synthesize mockMailComposeViewController;

+ (id)mailComposeViewController {
    return mockMailComposeViewController;
}

@end

Now we're ready for the test. I do a few things differently:

  • Allocate a testing subclass rather than the actual class (and don't leak!)
  • Set up the mock with an expectation, not just a stub
  • Inject the mock into the testing subclass
  • Verify the mock at the end

Here's the test:

- (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];
}

For completeness, we need one final test, and that's to guarantee that the factory method in the actual class returns what we expect:

- (void)testMailComposerViewControllerShouldBeCorrectType {
    STAssertTrue([[TestClass mailComposeViewController]
                 isKindOfClass:[MFMailComposeViewController class]], nil);
}
梦冥 2024-12-11 20:35:01

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];
}

Jon Reid's is a reasonable approach, though it seems that making mailComposeViewController a class method complicates it. And subclassing it in your test code means you'll always get the mocked version at test time, which may not be what you want. I'd make it an instance method. Then you can use a partial mock to override it at test time:

-(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];
}

If you keep it as a class method, you might consider making it a static global and exposing a way to override it:

static MFMailComposeViewController *mailComposeViewController = nil;

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

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

Then, your test would be similar to Jon's example:

-(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];
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文