OCMock класса с методом, вызываемым из метода test - PullRequest
1 голос
/ 17 сентября 2011

Я пытаюсь протестировать метод, который создает экземпляр 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];
}

В классе тестирования:

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

5 голосов
/ 18 сентября 2011

Вы создаете макет, но никогда не передаете его тестируемому классу или просите макет проверить себя.Вам нужна какая-то форма внедрения зависимости, чтобы сказать: «Вместо использования 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);
}
2 голосов
/ 20 сентября 2011

Jon Reid's - разумный подход, хотя кажется, что создание 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;
}

Тогда ваш тест будет похож на пример Джона:

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