TyphoonPatcher在unit testing中进行模拟

我有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 

这是我在测试中要做的事情:

 - (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]; } 

不幸的是我的setUp失败了下一条消息:

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

我做错了什么?

这里有一些额外的建议,以配合主要答案。

unit testing与集成测试:

在台风中,我们坚持传统的术语:

  • unit testing :与合作者隔离测试您的课程。 这是您注入测试双打的地方,如模拟或存根,而不是所有真正的依赖项。

  • 集成测试:使用真正的协作者测试您的课程。 虽然您可以修补我们的组件,以便将系统置于该测试所需的状态。

所以任何使用TyphoonPatcher测试都可能是集成测试。

更多信息: 台风集成测试

解决协作assembly:

这在早期版本的Typhoon中是必需的,但不再需要。 任何属于TyphoonAssembly的子类的属性都将被视为协作程序集。 删除以下内容:

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

测试实例化自己的程序集:

我们建议测试实例化并拆除他们的TyphoonComponentFactory。 优点是:

  • [TyphoonComponentFactory defaultFactory]是一个全局性的并且有一些缺点。
  • 集成测试可以定义自己的补丁,而不必担心将系统恢复到原始状态。
  • 除了使用TyphoonPatcher之外,如果您希望可以创建一个组件,其中某些组件的定义被覆盖。

你在Typhoon的部分遇到了糟糕的设计选择,但有一个简单的解决方法。

你正在使用这种方法:

 [patcher patchDefinition:[ui choiceController] withObject:^id{ return mock([MDChoiceViewController class]); }]; 

。 。 期待TyphoonDefinition作为参数。 在引导台风时:

  • 我们从一个或多个TyphoonAssembly子类开始,Typhoon仪器用于获取构建组件的配方。 然后丢弃这些TyphoonAssembly子clases。
  • 我们现在有一个TyphoonComponentFactory ,允许你的任何TyphoonAssembly接口在它面前。 (这样你可以拥有同一类的多个配置,同时仍然避免魔术字符串,允许在IDE中自动完成等)。

编写TyphoonPatcher时,它的设计适用于您为测试获得新TyphoonComponentFactory的情况(推荐),如下所示:

 //This is an actual TyphoonAssembly not the factory posing as an assembly 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 = [(MiddleAgesAssembly*) factory knight]; 

发生了什么:

所以问题是TyphoonPatcher期待TyphoonPatcher中的TyphoonDefinition ,而是从TyphoonAssembly获得一个实际的组件。

非常混乱,并且应该弃用获取修补程序的方式。

解:

请改用以下内容:

 [patcher patchDefinitionWithSelector:@selector(myController) withObject:^id{ return myFakeController; }];