Skip to content

1.x_Configuration Management & Testing

Jasper Blues edited this page Apr 26, 2014 · 3 revisions

Block Assembly | Xml Assembly | Autowiring | Using-assembled-components | Installing | Configuration Management & Testing


##Advanced Component Configuration


The container allows properties to be injected by value - these get converted to the required type at run-time. But sometimes its necessary to have different configuration values for eg Test-time vs Production.

One way to achieve this is to use a TyphoonPropertyPlaceholderConfigurer. Declare place-holders, as follows:

<component class="Knight" key="knight">        
    <property name="damselsRescued" value="${damsels.rescued}"/>
</component>

####Create a properties file with the values, as follows:

damsels.rescued=12
hasHorseWillTravel=no

####And then attach it to the container as a factory post processor:

TyphoonComponentFactory factory = [[TyphoonXmlComponentFactory alloc] initWithConfigFileName:@"MiddleAgesAssembly.xml"];
TyphoonPropertyPlaceholderConfigurer* configurer = [TyphoonPropertyPlaceholderConfigurer configurer];
[configurer usePropertyStyleResource:[TyphoonBundleResource withName:@"SomeProperties.properties"]];
[factory attachPostProcessor:configurer];

Knight* knight = [[TyphoonXmlComponentFactory defaultFactory] componentForKey:@"knight"];
//This now contains the value from the properties file. 
NSUInteger damselsRescued = knight.damselsRescued

####You can also define it directly in your assembly:

Block-style

- (id)propertyPlaceHolderConfigurer
{
    return [TyphoonDefinition propertyPlaceholderWithResource:[TyphoonBundleResource withName:@"SomeProperties.properties"]];
}

Xml-style

<property-placeholder location="SomeProperties.properties"/>

##Registering Additional Type Converters

Create a class that conforms to the following:

@protocol TyphoonTypeConverter <NSObject>

- (id)supportedType;

- (id)convert:(NSString*)stringValue;

@end

. . . And then register it with the container as follows:

[[TyphoonTypeConverterRegistry shared] register:[[YourConverterClass alloc] init]];

Or directly define it in your Assembly, it's automagically picked up by Typhoon:

- (id)myTypeConverter
{
    return [TyphoonDefinition withClass:[YourConverterClass class]];
}

##Resolving NIB files for UIViewControllers If you are using NIBs for your iOS UIViewController classes you can use the _TyphoonViewControllerNibResolver_ class to cut down your boiler plate.

The class, if defined in the assembly, is a picked up automatically by Typhoon and completes the initialiser of UIViewController definitions with the standard -initWithNibName:bundle:

The default behaviour is using the class name as the NIB name. If you are working according to another scheme the behaviour can easily be modified by subclassing and overriding -resolveNibNameForClass:

The full definition like this:

- (id)myViewController
{
    return [TyphoonDefinition withClass:[MyViewController class] initialization:^(TyphoonInitializer* initializer)
    {
        initializer.selector = @selector(initWithNibName:bundle:);
        [initializer injectWithValueAsText:@"MyViewController" requiredTypeOrNil:[NSString class]];
        [initializer injectWithObject:[NSBundle mainBundle]];
    }];

Becomes this:

- (id)nibResolver
{
    return [TyphoonDefinition withClass:[TyphoonViewControllerNibResolver class]];
]}

- (id)myViewController
{
    return [TyphoonDefinition withClass:[MyViewController class]];
}

Test Cases

Typhoon works with whatever test runners, matching libraries, and mocking libraries you like to use. Here's an example of using Typhoon with OCUnit:

@implementation LoyaltyCardDaoTests
{
    id <LoyaltyCardDao> _loyaltyCardDao;
}

- (void)setUp
{
    ApplicationAssembly* assembly = (ApplicationAssembly*) 
        [TyphoonBlockComponentFactory factoryWithAssembly:[ApplicationAssembly assembly]];
    _loyaltyCardDao = [assembly loyaltyCardDao];
}

- (void)test_should_allow_finding_loyalty_card_by_id
{
    
}

@end


Patching Out A Component

You can patch out a component as follows:

MiddleAgesAssembly* assembly = [MiddleAgesAssembly assembly];
TyphoonComponentFactory* factory = [TyphoonBlockComponentFactory factoryWithAssembly:assembly];

TyphoonPatcher* patcher = [[TyphoonPatcher alloc] init];
[patcher patchDefinition:[assembly knight] withObject:^id
{
    Knight* mockKnight = mock([Knight class]);
    [given([mockKnight favoriteDamsels]) willReturn:@[
        @"Mary",
        @"Janezzz"
    ]];

    return mockKnight;
}];

[factory attachPostProcessor:patcher];

Knight* knight = [factory componentForKey:@"knight"];



Asynchronous Integration Testing

It's very common for iOS & OSX applications to have components that collaborate with the main thread via background threads. One example is making a network request on a background thread, and then updating the user interface with new data when it completes.

Many test libraries now support asynchronous testing. Or, if you like, you can use Typhoon's simple and easy approach as follows:

- (void)test_should_retrieve_a_weather_report_given_a_valid_city
{
    __block WeatherReport* receivedReport;
    [weatherClient loadWeatherReportFor:@"Manila" onSuccess:^(WeatherReport* report)
    {
        receivedReport = report;
    }];

    
    [TyphoonTestUtils waitForCondition:^BOOL
    {
        typhoon_asynch_condition(receivedReport != nil);
    }];
}

If the condition does not occur before the time-out (default is seven seconds, checked very 1 second), then an exception will be raised, and the test will fail.


####Performing additional asynchronous assertions:

Once the initial condition has been set, you can perform extra assertions or logging as follows:

- (void)test_should_retrieve_a_weather_report_given_a_valid_city
{
    __block WeatherReport* receivedReport;
    [weatherClient loadWeatherReportFor:@"Manila" onSuccess:^(WeatherReport* report)
    {
        receivedReport = report;
    }];
    
    [TyphoonTestUtils waitForCondition:^BOOL
    {
        return receivedReport != nil;
    } andPerformTests^
    {
        //Use any kind of matchers that you like here - standard OCUnit, OCHamcrest, Kiwi, Expecta, etc. 
    }];
}

####Overriding default time-out.

You can override the default time-out (of seven seconds) using the following:

- (void)test_should_retrieve_a_weather_report_given_a_valid_city
{
    __block WeatherReport* receivedReport;
    [weatherClient loadWeatherReportFor:@"Manila" onSuccess:^(WeatherReport* report)
    {
        receivedReport = report;
    }];
    
    [TyphoonTestUtils wait:2 secondsForCondition:^BOOL
    {
        return receivedReport != nil;
    } andPerformTests^
    {
        //Tests 
    }];
}

####AppCode LiveTemplate

If you use AppCode you can create a quick keyboard shortcut to define an integration test template using Live Templates.

  • Open Preferences/Live Templates
  • Define a template with the keyboard shortcut 'itest', as follows:
[TyphoonTestUtils wait:7 secondsForCondition:^BOOL
{
    typhoon_asynch_condition($condition$);
} andPerformTests:^
{
    $tests$
}];

githalytics.com alpha