Byron Duenas
Byron Duenas

Reputation: 161

How can I access local variables within a method for an XCTestCase using OCMock?

I have a method that I want to test that does this:

- (void)openEmailFeedback
{
    MFMailComposeViewController* controller = [[MFMailComposeViewController alloc] init];
    controller.mailComposeDelegate = self;
    [controller setToRecipients:@"[email protected]"];
    [controller setSubject:@""];
    [controller setMessageBody:@"" isHTML:NO];
    [self presentViewController:controller animated:YES completion:nil];
}

And I tried testing it with

- (void)testOpenEmailFeedback
{
    ViewController *vc = [[ViewController alloc] init];

    // Create a partial mock of UIApplication
    id mockMailComposeViewController = [OCMockObject mockForClass:[MFMailComposeViewController class]];
    [[mockMailComposeViewController expect] setMailComposeDelegate:vc];
    [[mockMailComposeViewController expect] setToRecipients:@[@"[email protected]"]];
    [[mockMailComposeViewController expect] setSubject:@""];
    [[mockMailComposeViewController expect] setMessageBody:@"" isHTML:NO];

    [vc openEmailFeedback];

    [mockMailComposeViewController verify];
    [mockMailComposeViewController stopMocking];
}

But I realized that mockMailComposeViewController is nowhere near being the same variable as the local MFMailComposeViewController *controller that's in the method. Is it possible to some how access a local variable within a method "openEmailFeedback" when testing?

Upvotes: 2

Views: 1382

Answers (2)

hvkale
hvkale

Reputation: 17787

I generally use this pattern for mocking the locally created objects within the method I am testing-

MyClass *instance = [[MyClass alloc] init];
id mockMyClassObj = [OCMockObject mockForClass:[MyClass class]];
[[[mockMyClassObj stub] andReturn:instance] alloc];

And then in asserts -

// Lets say I am testing for property 'whatever' being nil after calling the method I am testing
XCTAssertNil(instance.whatever, @"whatever should be nil");

Here is the Xcode snippet for that code -

<#Class#> *<#instance#> = [[<#Class#> alloc] init];
id mock<#name#> = [OCMockObject mockForClass:[<#class#> class]];
[[[mock<#name#> stub] andReturn:<#instance#>] alloc];

Upvotes: 0

Erik Doernenburg
Erik Doernenburg

Reputation: 3016

The standard answer to this question is to use the dependency injection pattern. With OCMock and partial mocks there is another pattern that you can use, too. Simply create a factory method inside the class that has the dependency and stub that. In your ViewController:

- (MFMailComposeViewController *)createComposeViewController
{
    return [[MFMailComposeViewController alloc] init];
}

- (void)openEmailFeedback
{
    MFMailComposeViewController* controller = [self createComposeViewController];
    controller.mailComposeDelegate = self;
    // continue as normal...

In your test:

- (void)testOpenEmailFeedback
{
ViewController *vc = [[ViewController alloc] init];

// Create a partial mock of UIApplication
id mockMailComposeViewController = [OCMockObject mockForClass:[MFMailComposeViewController class]];
[[mockMailComposeViewController expect] setMailComposeDelegate:vc];
[[mockMailComposeViewController expect] setToRecipients:@[@"[email protected]"]];
[[mockMailComposeViewController expect] setSubject:@""];
[[mockMailComposeViewController expect] setMessageBody:@"" isHTML:NO];

 id vcPartialMock = [OCMockObject partialMockForObject:vc];
 [[[vcPartialMock stub] andReturn:mockMailComposeViewController] createComposeViewController];

 // continue as before...  

Upvotes: 4

Related Questions