@@ -353,8 +353,109 @@ describe (@"FRPPhotoViewModel", ^{
353353现在来看这个复杂的初始化方法,这东西看起来真巨大!近20行纯粹的未经测试的代码。哎呀!让我们来一点点简化这个事情,并逐步加上我们的测试代码。
354354
355355``` Objective-C
356+ - (instancetype )initWithModel:(FRPPhotoModel *)photoModel {
357+ self = [super initWithModel:photoModel];
358+ if(!self) return nil;
359+
360+ @weakify(self);
361+ [self.didBecomeActiveSignal subscribeNext:^(id x) {
362+ @strongify(self);
363+ [self downloadPhotoModelDetails];
364+ }];
365+
366+ RAC (self, photoImage) = [ RACObserve(self.model, fullsizedData) map:^id (id value) {
367+ return [ UIImage imageWithData: value ] ;
368+ }] ;
369+
370+ return self;
371+ }
372+
373+ - (void )downloadPhotoModelDetails {
374+ self.loading = YES;
375+ [[FRPPhotoImporter fetchPhotoDetails:self.model] subscribeError:^(NSError *error) {
376+ NSLog(@"Could not fetch photo details : %@",error);
377+ } completed:^ {
378+ self.loading = NO;
379+ NSLog(@"Fetched photo details.");
380+ }];
381+ }
382+
383+ ```
384+
385+ 我们选择了不直接测试` fetchPhotoDetails: ` ,所以我们把它置于一个实例方法中,以便更容易对它进行测试。这个方法(即` fetchPhotoDetails: ` )实现的细节在这里对我们不重要。
386+
387+ 现在开始写关于它的测试代码吧:
388+
389+ ``` Objective-C
390+ it (@"should download photo model details when it becomes active", ^{
391+ FRPPhotoViewModel * viewModel = [[ FRPPhotoViewModel alloc] initWithModel: nil ] ;
392+
393+ id mockViewModel = [ OCMockObject partialMockForObject: viewModel ] ;
394+ [[ mockViewModel expect] downloadPhotoModelDetails] ;
395+
396+ [ mockViewModel setActive: YES ] ;
397+ [ mockViewModel verify] ;
398+ });
399+ ```
400+
401+ 注意看初始化方法中不产生(函数)副作用而是把这种副作用放在订阅`didBecomeActiveSignal`的Block块中时,测试视图模型的代码是多么简单!
402+
403+ 现在我们需要测试剩下的那些视图模型,他们全部非常简单。我们使用更少的mock,因为很多的业务逻辑仅仅是视图模型的model值到他自己的属性的映射。
404+
405+ ```Objective-C
406+ it (@"should return the photo's name property when photoName is invoked", ^{
407+ NSString *name = @"Ash";
408+
409+ id mockPhotoModel = [OCMockObject mockForClass:[FRPPhotoModel class]];
410+ [[[mockPhotoModel stub] andReturn:name] photoName];
411+
412+ FRPPhotoViewModel *viewModel = [[FRPPhotoViewModel alloc] initWithModel:nil];
413+ id mockViewModel = [OCMockObject partialMockForObject:viewModel];
414+ [[[mockViewModel stub] andReturn:mockPhotoModel] model];
415+
416+ id returnedName = [mockViewModel photoName];
417+
418+ expect(returnedName).to.equal(name);
419+
420+ [mockPhotoModel stopMocking];
421+ });
422+
423+ it (@"should correctly map image data to UIImage", ^{
424+ UIImage *image = [[UIImage alloc] init];
425+ NSData *imageData = [NSData data];
426+
427+ id mockImage = [OCMockObject mockForClass:[UIImage class]];
428+ [[[mockImage stub] andReturn:image] imageWithData:imageData];
429+
430+ FRPPhotoModel *photoModel = [[FRPPhotoModel alloc] init];
431+
432+ photoModel.fullsizedData = imageData;
433+
434+ __unused FRPPhotoViewModel *viewModel = [[FRPPhotoViewModel alloc] initWithModel:photoModel];
435+
436+ [mockImage verify];
437+ [mockImage stopMocking];
438+
439+ });
440+
441+ it(@"should return the correct photo name", ^{
442+ NSString *name = @"Ash";
443+
444+ FRPPhotoModel *photoModel = [[FRPPhotoModel alloc] init];
445+ photoModel.photoName = name;
446+
447+ FRPPhotoViewModel *viewModel = [[FRPPhotoViewModel alloc] initWithModel:photoModel];
448+
449+ NSString *returnedName = [viewModel photoName];
450+
451+ expect(name).to.equal(returnedName);
452+ });
356453
357454```
358455
456+ 这就是为视图模型撰写单元测试的全部内容了。
457+
458+ 在理想的情况下,单元测试能帮助改进你的代码质量。小巧而高内聚的方法比随意的满是副作用的方法更招人待见。它简单而完美地诠释了函数响应型编程的精髓。
359459
460+ 测试MVVM的好处是:我们不用触及UIKit。请记住,写得好的MVVM视图模型的特点是:该视图模型不会与用户交互的接口类有任何交互。
360461
0 commit comments