Search code examples
iosunit-testingocmock

unit test local objects or dependency injection with OCMock?


Trying to create simple test for following function:

-(void)presentWithString:(NSString *)name
{
    CustomVC *customVC = [[CustomVC alloc] initWithName:name];
    UINavigationController *nav = [[UINavigationController alloc] init];
    nav.viewControllers = @[customVC];

    dispatch_async(dispatch_get_main_queue(), ^{
        [self.vc presentViewController:nav animated:YES completion:nil];
    });
}

I can split this into chunks with dependency injection, but don't know how to write proper test either way. What would be the best practice for this example?


Solution

  • What do you want to test? There are 3 things happening in your method :

    1. CustomVC is created with name passed.
    2. CustomVC is embedded inside navigation controller.
    3. The navigation controller is presented on self.vc.

    You can write a test that checks the whole flow :

    - (void)testPresentWithString_shouldPresentCustomVC_withPassedName {
    
        // Arrange
        NSString *expectedName = @”name”;
        XCTestExpectation *exp = [self expectationWothDescription:@”presentVC called”];
    
        TestClass *sut = [[TestClass alloc] init];
        id vcMock = OCMClassMock([UIViewController class]);
        sut.vc = vcMock;
    
        OCMExpect([vcMock presentViewController:OCM_ANY animated:YES completion:nil]).andDo(^(NSInvocation *invocation) {
    
            UINavigationController *nav = nil;
            [invocation getArgument:&nav atIndex:2];
    
            CustomVC *custom = nav.viewControllers.firstObject;
    
            // Assert
            XCTAssertNotNil(nav);
            XCTAssertTrue([nav isKindOfClass:[UINavigationController class]]);
            XCTAssertEqual(nav.viewControllers.count, 1);
            XCTAssertNotNil(custom);
            XCTAssertTrue([custom isKindOfClass:[CustomVC class]]);
            XCTAssertEqual(custom.name, expectedName);
    
            [exp fulfill];
        });
    
        // Act
        [sut presentWithString:expectedName];
    
        // Assert
        [self waitForExpectationsWithTimeout:1 handler:nil];
        OCMVerifyAll(vcMock);
    
        // Cleanup
        [vcMock stopMocking];
    }
    

    This code checks everything that happens in your method - that a method got called with specific arguments, that the first of these arguments was a navigation controller with only CustomVC embedded and that this CustomVC had name set. Obviously I’ve made assumptions that vc property on tested class can be set from outside and that the name on CustomVC can be read. If not, it may be trickier to test some parts of this.

    Personally I wouldn’t unit test this. I would test the initialization of CustomVC separately, and put the whole presentation under a UI test.

    Let me know if everything is clear!

    Side note : I wrote this on mobile from memory, so there might be small mistakes in the code. I will update it when I have a chance to check it with Xcode.