Search code examples
iosunit-testingtyphoon

TyphoonPatcher for mocking in unit tests


I have Assembly:

@interface MDUIAssembly : TyphoonAssembly

@property (nonatomic, strong, readonly) MDServiceAssembly *services;
@property (nonatomic, strong, readonly) MDModelAssembly *models;

- (id)choiceController;

@end

@implementation MDUIAssembly

- (void)resolveCollaboratingAssemblies
{
    _services = [TyphoonCollaboratingAssemblyProxy proxy];
    _models = [TyphoonCollaboratingAssemblyProxy proxy];
}

- (id)choiceController
{
    return [TyphoonDefinition withClass:[MDChoiceViewController class]
                          configuration: ^(TyphoonDefinition *definition) {
        [definition useInitializer:@selector(initWithAnalytics:diary:)
                        parameters: ^(TyphoonMethod *initializer) {
            [initializer injectParameterWith:[_services analytics]];
            [initializer injectParameterWith:[_models diary]];
        }];
    }];
}

@end

Here what I'm trying to do in tests:

- (void)setUp
{
    patcher = [TyphoonPatcher new];
    MDUIAssembly *ui = (id) [TyphoonComponentFactory defaultFactory];
    [patcher patchDefinition:[ui choiceController] withObject:^id{
       return mock([MDChoiceViewController class]);
    }];
    [[TyphoonComponentFactory defaultFactory] attachPostProcessor:patcher];
}

- (void) tearDown 
{
   [super tearDown];
   [patcher rollback];
}

Unfortunately my setUp fails with next message:

-[MDChoiceViewController key]: unrecognized selector sent to instance 0xbb8aaf0

What I'm doing wrong?


Solution

  • Here's some extra advice to go along with the main answer . . .

    Unit Tests vs Integration Tests:

    In Typhoon we adhere to the traditional terms:

    • Unit Tests : Testing your class in isolation from collaborators. This is where you inject test doubles like mocks or stubs in place of all of the real dependencies.

    • Integration Tests: Testing your class using real collaborators. Although you may patch our a component in order to put the system in the required state for that test.

    So any test that uses TyphoonPatcher would probably be an integration test.

    More info here: Typhoon Integration Testing

    Resolve Collaborating Assemblies:

    This was required in earlier version of Typhoon, but is not longer needed. Any properties that are are sub-class of TyphoonAssembly will be treated as collaborating assemblies. Remove the following:

    - (void)resolveCollaboratingAssemblies
    {
        _services = [TyphoonCollaboratingAssemblyProxy proxy];
        _models = [TyphoonCollaboratingAssemblyProxy proxy];
    }
    

    Tests instantiate their own assembly:

    We recommend that tests instantiate and tear down their on TyphoonComponentFactory. The advantages are:

    • [TyphoonComponentFactory defaultFactory] is a global and has some drawbacks.
    • Integration tests can define their own patches without having to worry about putting the system back in the original state.
    • In addition to using TyphoonPatcher, if you wish you can create an assembly where the definitions for some components are overridden.