@@ -272,7 +272,89 @@ describe(@"FRPGalleryViewModel",^{
272
272
273
273
为了测试一个方法,测试代码也太多了吧! 我知道,我知道~ 这是OCMock没落的原因之一,它竟然需要这么多的模板。但你不能责怪它,因为它要工作在令它不寒而栗的Objective-C平台上!
274
274
275
+ 我们创建了一个` FRPGalleryViewModel ` 的mock版本,告诉它期望` importPhotoSignal ` 被调用。然后才进行对象的初始化。这里使用了一点点技巧,因为我们在mockObject上调用了init方法,但它(init)实际上是一个NSProxy的子类。然后,对OCMock来讲,它足够聪明,它了解这一切,有能力做出正确的选择。只是看起来有点诡异罢了。我们使用` [mockObject init] ` 给` mockObject ` 赋值,也是为了屏蔽编译警告。最后我们验证了所有预期可能被调用的方法。
275
276
277
+ 这个例子中表现出来的测试很困难的情况也说明了另一个问题,你应该避免视图模型的初始化方法产生"副作用"(参见前面章节提到的“函数的副作用”),应该使用` didBecomeActiveSignal ` 来代理。
278
+
279
+ 下面我们来测试` FRPPhotoViewModel ` .再次突出引起函数副作用和使用` didBecomeActiveSignal ` 的区别。
280
+
281
+ 快速浏览下实现:
282
+
283
+ ``` Objective-C
284
+
285
+ @implementation FRPPhotoViewModel
286
+
287
+ - (intancetype)initWithModel:(FRPPhotoModel * )photoModel {
288
+ self = [ super initWithModel: photoModel ] ;
289
+ if(!self) return nil;
290
+
291
+ @weakify (self);
292
+ [ self.didBecomeActiveSignal subscribeNext:^ (id x) {
293
+ @strongify (self);
294
+ self.loading = YES;
295
+ [[ FRPPhotoImporter fetchPhotoDetails: self .model]
296
+ subscribeError: ^ (NSError * error) {
297
+ NSLog(@"Could not fetch photo details: %@",error);
298
+ }
299
+ completed: ^ {
300
+ self.loading = NO;
301
+ NSLog(@"Fetched photo details");
302
+ }] ;
303
+ }] ;
304
+
305
+ RAC(self, photoImage) = [ RACObserve(self.model, fullsizedData) map:^id (id value) {
306
+ return [ UIImage imageWithData: value ] ;
307
+ }] ;
308
+
309
+ return self;
310
+ }
311
+
312
+ - (NSString * )photoName {
313
+ return self.model.photoName;
314
+ }
315
+
316
+ @end
317
+
318
+ ```
319
+ 首先我们来测试`photoName`方法:
320
+
321
+ ```Objective-C
322
+ #import <Specta/Specta.h>
323
+ #define EXP_SHORTHAND
324
+ #import <Expecta/Expecta.h>
325
+ #import <OCMock/OCMock.h>
326
+
327
+ #import "FRPPhotoViewModel.h"
328
+ #import "FRPPhotoModel.h"
329
+
330
+ SpecBegin(FRPPhotoViewModel)
331
+
332
+ describe (@"FRPPhotoViewModel", ^{
333
+ it(@"should return the photo's name property when photoName is invoked", ^{
334
+ NSString *name = @"Ash";
335
+
336
+ id mockPhotoModel = [OCMockObject mockForClass:[FRPPhotoModel class]];
337
+ [[[mockPhotoModel stub] andReturn:name] photoName];
338
+
339
+ FRPPhotoViewModel *viewModel = [[FRPPhotoViewModel alloc] initWithModel:nil];
340
+ id mockViewModel = [OCMockObject partialMockForObject:viewModel];
341
+ [[[mockViewModel stub] andReturn:mockPhotoModel] model];
342
+
343
+ id returnName = [mockViewModel photoName];
344
+
345
+ expect(returnedName).to.equal(name);
346
+ [mockPhotoModel stopMocking];
347
+ });
348
+ });
349
+
350
+ ```
351
+ 我们为mock的视图模型的model属性添加了一个mockPhotoModel,它会mocks所有的途径。
352
+
353
+ 现在来看这个复杂的初始化方法,这东西看起来真巨大!近20行纯粹的未经测试的代码。哎呀!让我们来一点点简化这个事情,并逐步加上我们的测试代码。
354
+
355
+ ``` Objective-C
356
+
357
+ ```
276
358
277
359
278
360
0 commit comments