简体   繁体   中英

How to inject fake, stubbed or mock dependencies for Integration tests using Typhoon

I'm trying to write integration tests using KIF. My question is:

How to inject stubbed, mock or fake dependency for particular view controller?

Each view controller using dependencies like a data model, http client, store manager etc. comes from ModelAssembly, ApplicationAssembly, ManagerAssmebly.

On storyboard, for login view i have a key path, containing value "loginViewController".

Creating view controllers:

ViewControllersAssembly.h

@interface ViewControllersAssembly : TyphoonAssembly
@property (nonatomic, strong) ModelAssembly *modelAssembly;

- (id)loginViewController;
@end

ViewControllersAssembly.m

@implementation ViewControllersAssembly
- (UIViewController *)loginViewController {
return [TyphoonDefinition withClass:[LoginViewController class] configuration:^(TyphoonDefinition *definition) {
    [definition injectProperty:@selector(userModel) with:[self.modelAssembly userModel]];
}];

}

UserModel have method to login

- (RACSingnal*)loginWithEmail:(NSString*)email password:(NSString*)password;

Now in integration tests target i have class like:

LoginTests.h

@interface LoginTests : KIFTestCase
@property (nonatomic, strong) UserModel *fakeUserModel;
@end

LoginTests.m

@implementation LoginTests

- (void)beforeAll {
    self.fakeDataModel = [self mockDataModel];
}

- (void)testLogin {
    [self.fakeDataModel mockNextResponse:[RACSignalHelper getGeneralErrorSignalWithError:[[NSError alloc] initWithDomain:@"http://some.com" code:452 userInfo:nil]]];
    [tester waitForViewWithAccessibilityLabel:@"loginScreen"];

    [tester enterText:@"user@gmail.com" intoViewWithAccessibilityLabel:@"emailAdress"];
    [tester enterText:@"asd123" intoViewWithAccessibilityLabel:@"password"];
    [tester tapViewWithAccessibilityLabel:@"loginButton"];

    [tester tapViewWithAccessibilityLabel:@"OK"];
    // for example error code 542 we should display alert with message "User Banned"
    // now somehow check that UIAlertView localizedDescription was "User Banned" 
}

- (FakeUserModel *)mockUserModel {
    ModelAssembly *modelAssembly = [[ModelAssembly assembly] activate];
    TyphoonPatcher *patcher = [[TyphoonPatcher alloc] init];
    [patcher patchDefinitionWithSelector:@selector(userModel) withObject:^id{
        return [FakeUserModel new];
     }];

    [modelAssembly attachDefinitionPostProcessor:patcher];
    return [modelAssembly userModel];
}

FakeUserModel is class that override UserModel class, adding possibility to stub response for next called request.

that solution not is not working.

How and where i should pass FakeUserModel?

1) i'd like to have access to injected instance

2) injected instance must be of type FakeUserModel, which is only in integration tests target.

3) i don't want to modify production code for integration tests.

Unit Testing

If you wish to replace all dependencies for a given class with a test double, and thus test a class in isolation from its collaborators, this would be a unit test. Simply instantiate an instance for testing, passing in your test doubles (mock, stub, etc) as collaborators.

Integration Testing

If you wish to patch-out one or more instances in an assembly with a test double, to put the system into the required state for an integration test, Typhoon provides several approaches.

You can patch out a component as follows:

MiddleAgesAssembly* assembly = [[MiddleAgesAssembly assembly] activate];

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

    return mockKnight;

}];

[assembly attachPostProcessor:patcher];

Knight* knight = [(MiddleAgesAssembly*) factory knight]

More information on this approach can be found in the Integration Testing section of the user guide.

Modularization

Alternatively you could modularize your assembly, and activate with a sub-class or alternative implementation, that provides another implementation of certain classes, example:

UIAssembly *uiAssembly = [[UIAssembly new] 
    activateWithCollaboratingAssemblies:@[
        [TestNetworkComponents new], //<--- Patched for testing
        [PersistenceComponents new]];

SignUpViewController* viewController = [uiAssembly signUpViewController];

More information on this approach can be found in the modularization section of the user guide.

If you want to patch-out the assembly that is used by the storyboard and initialized using Plist integration, then you can make that assembly default by calling:

[yourAssembly makeDefault];

and you can get this assembly in your test case by calling:

[yourAssembly defaultAssembly];

and after that, you can easily patch some definitions. It's important to make your assembly default before test starts, so maybe app delegate will be a good place for that. This is probalby not the best solution, but it looks like you want to achieve some global access to assembly.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM