【问题标题】:Unit testing Parse framework iOS单元测试 Parse 框架 iOS
【发布时间】:2014-03-13 16:06:57
【问题描述】:

我正在尝试为使用 Parse 后端框架的 iOS 应用程序编写单元测试,经过大量实验后,似乎无法编写成功的单元测试。我发现了一些关于测试异步代码 (Testing asynchronous call in unit test in iOS) 和测试网络调用的帖子,但是我还没有找到一种方法来测试对 Parse 后端的异步回调调用。

举个例子,谁能告诉我如何测试以下代码行:

[PFUser saveUser:userModelMock withBlock:^(BOOL success, NSError *error) {

}];

我正在使用 OCMock 和 XCTest 框架。

任何帮助将不胜感激。

* 编辑 * 这是我目前所拥有的,但似乎失败了

- (void)testSaveUser {
    id userModelMock = [OCMockObject niceMockForClass:[UserModel class]];
    id userControllerMock = [OCMockObject niceMockForClass:[self.userController class]];

    [[userModelMock expect] saveInBackgroundWithBlock:[OCMArg any]];
    [[userControllerMock stub] saveUser:userModelMock withBlock:[OCMArg any]];

    [userModelMock verify];
}

【问题讨论】:

  • 您要测试什么? PFUser 希望有自己的单元测试,所以你的测试应该依赖它像宣传的那样运行,你可以简单地模拟对 saveUser:withBlock: 的调用。如果您想测试您的异步回调是否按预期工作,这个答案可能会有所帮助:stackoverflow.com/a/20694495/449161
  • 同意,我想 Parse 有内部测试 - 但是我将 PFUser 子类化并创建了一个控制器来处理所有数据交互。在控制器中我有一个调用 saveUser 的方法,所以在测试中我需要验证 saveUser: 在我调用包装它的方法时被调用。
  • 如果您正在测试控制器从另一个方法调用此方法,只需对 PFUser 对象使用模拟和 expects 并从您的测试中调用控制器方法。如果调用是异步的,请遵循您链接的帖子中的模型。还是我错过了什么?
  • 这正是我正在做的。但是我不想调用 parse 方法,我只想知道它会被调用。我是单元测试的新手,并且仍在学习曲线中工作。我将编辑帖子并坚持我已经拥有的内容。

标签: ios unit-testing parse-platform ocmock xctest


【解决方案1】:

使用https://github.com/hfossli/AGAsyncTestHelper/ 中的宏WAIT_WHILE(<expression>, <time_limit>),您可以编写

- (void)testSaveUser
{
    __block BOOL saved = NO;
    [PFUser saveUser:userModelMock withBlock:^(BOOL success, NSError *error) {
        saved = YES;
    }];
    WAIT_WHILE(!saved, 10.0, @"Failed to save user within 10 seconds timeframe);
}

【讨论】:

    【解决方案2】:

    原来这只是因为我缺乏对单元测试的理解。在对模拟对象、存根等进行了一些研究之后,我想出了:

    - (void)testSaveUser {
        id userModelMock = [OCMockObject mockForClass:[UserModel class]];
        id userControllerMock = [OCMockObject partialMockForObject:self.userController];
    
        [[userModelMock expect] saveInBackgroundWithBlock:[OCMArg any]];
        [userControllerMock saveUser:userModelMock withBlock:[OCMArg any]];
    
        [userModelMock verify];
    }
    

    【讨论】:

    • 是的,看起来您最初并没有调用您想要测试的方法!我认为self.userController 正在返回一个由您的测试类为每个测试创建的用户控制器对象。很高兴你取得了进展。
    • 只是说在您要测试的方法中将 [OCMArg any] 作为块传递是没有意义的。而是传递一个假块或 nil,具体取决于您要测试的内容
    【解决方案3】:

    如果您不介意使用第三方库,可以使用以下方法:

    #import <XCTest/XCTest.h>
    #import <RXPromise/RXPromise.h>
    ...
    
    @interface PFUserTests : XCTestCase
    @end
    
    @implementation PFUserTests
    
    // helper:
    - (RXPromise*) saveUser:(User*)user {
        RXPromise* promise = [[RXPromise alloc] init];
        [PFUser saveUser:user withBlock:^(Bool success, NSError*error){
            if (success) {
                [promise fulfillWithValue:@"OK"];
            }
            else {
                [promise rejectWithReason:error];
            }
        }];
        return promise;
    }
    
    
    // tests:
    
    -(void) testSaveUser 
    {
        User* user = ... ;
        RXPromise* promise = [self saveUser:user];
    
        // set a timeout:
        [promise setTimeout:5.0];
    
        [promise.thenOn(dispatch_get_main_queue(), ^id(id result) {
            XCTAssertTrue([result isEqualToString:@"OK"], @"");
            XCTAssertTrue( ... , @"");
            return nil;
        }, ^id(NSError* error) {
            // this may fail either due to a timeout or when saveUser fails:
            XCTFail(@"Method failed with error: %@", error);
            return nil;
        }) runLoopWait];  // suspend the run loop, until after the promise gets resolved 
    
    }
    

    几点说明:

    您可以在任何队列上执行成功或失败处理程序(使用thenOn 语句为promise 注册),只需指定队列即可。这不会死锁主线程,即使处理程序在主线程上显式执行,并且测试也在主线程上运行(感谢运行循环机制)。

    方法runLoopWait 将进入一个运行循环,并且只有在promise 被解决后才返回。这既有效又高效。

    promise 接受超时。如果超时过期,promise 将被“拒绝”(即解决)并出现相应的错误。

    IMO,Promise 是处理常见异步编程问题的宝贵工具,而不仅仅是单元测试的助手。 (另见:维基文章Futures and promises

    请注意,我是RXPromise library 的作者,因此我完全有偏见;)

    Objective-C 还有其他 Promise 实现。

    【讨论】:

    • 谢谢你,我看了看,RXPromise 看起来是一个非常有用的框架,所以做得很好。也许我没有完全解释自己;我不想直接调用 saveUser:withBlock: 方法,因为它会进行网络调用,这显然不适合测试。有没有办法可以模拟这个?再次感谢。
    • 不需要第 3 方库 - 您可以使用 XCTestExpectation。 NSHipster 上提供的教程:nshipster.com/xctestcase
    • @PaulArdeleanu 感谢您的更新。 XCTestExpectation 目前不可用。恕我直言,promise 比 XCTestExpectation 更易于使用且用途更广泛。
    猜你喜欢
    • 2010-09-10
    • 2023-04-10
    • 2023-03-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-11
    • 2018-06-17
    • 2016-06-15
    相关资源
    最近更新 更多