@@ -272,7 +272,89 @@ describe(@"FRPGalleryViewModel",^{
272272
273273为了测试一个方法,测试代码也太多了吧! 我知道,我知道~ 这是OCMock没落的原因之一,它竟然需要这么多的模板。但你不能责怪它,因为它要工作在令它不寒而栗的Objective-C平台上!
274274
275+ 我们创建了一个` FRPGalleryViewModel ` 的mock版本,告诉它期望` importPhotoSignal ` 被调用。然后才进行对象的初始化。这里使用了一点点技巧,因为我们在mockObject上调用了init方法,但它(init)实际上是一个NSProxy的子类。然后,对OCMock来讲,它足够聪明,它了解这一切,有能力做出正确的选择。只是看起来有点诡异罢了。我们使用` [mockObject init] ` 给` mockObject ` 赋值,也是为了屏蔽编译警告。最后我们验证了所有预期可能被调用的方法。
275276
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+ ```
276358
277359
278360
0 commit comments