从视图控制器上的方法内部测试/模拟称为 class 方法的 UIAlertView

test / mocking for a UIAlertView called as class method from inside of method on view controller

我正在为视图控制器中的 performLogin 方法编写测试,但我无法重构原始方法,也无法重构项目中的任何其他文件。我只能修改我的 LoginTest.m 文件。我正在尝试使用 OCMock 在不更改原始程序文件的情况下完成此测试。

我正在尝试验证在 performLogin 方法收到异常时是否显示 UIAlertView。 ViewHelper class 有一个 class 方法,该方法 returns 无效并创建并显示 UIAlertView。

我已经模拟了调用 ViewHelper 的视图控制器,并且我尝试使用 OCMock 来测试 UIAlertView class 使用来自各种搜索的示例,但是这些测试失败了,因为它没有捕捉到创建警报视图。

我是 OCMock 的新手,我不确定如何为此类测试模拟 ViewHelper 文件,尤其是当它是从方法中调用而不是锚定到 属性 时。

可能有一种简单的方法可以做到这一点,但我还没有弄清楚,如果您能提供帮助,我们将不胜感激。

提前致谢。

调用 doLogin 时在方法内部 returns TRUE 或抛出异常。当抛出异常时,它应该弹出一个带有消息的 UIAlertView。我想测试这部分代码并验证 UIAlertView 是否实际显示。

请注意,ViewHelper 是直接从 catch 块中调用的。

该方法如下所示:

-(IBAction)performLogin:(id)sender {
  if(valid) 
  {
    @try
    {
      //loginModel is a private instance variable
      loginModel = [[Services sharedInstance] getLoginModel];
      [loginModel doLoginWithUsername:username andPassword:password];
      [self performSegueWithIdentifier:@"showWelcomeScreen" sender:self];
    }
    @catch(NSException *e)
    {

      //I NEED TO TEST THAT THIS CALL OCCURS AND THE UIAlertView is shown
      //Not sure how to do this as it's not anchored to a property or method
      //and is called as a class method

      [ViewHelper showAlertForTitle:"Error" andTheMessage:@"network error"
        andAccessibilityLabel:@"Network Error"];
    }
   }
}

ViewHelpder.m 看起来像这样 - 注意,它是一个 class 方法:

+(void) showAlertForTitle:(NSString *)title andTheMessage:(NSString *)message      andAccessibilityLabel:(NSString *)label
{
    NSLog(@"reached alert notification class");
    UIAlertView *successAlert = [[UIAlertView alloc] initWithTitle:title  message:message delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];
    [successAlert setAccessibilityLabel:label];
    [successAlert show];
}

除其他事项外,我目前已经尝试过:

- (void)testForfailedLogin {

    //call singleton first to initialize mocked login model
    [[Services sharedInstance] getLoginModel];

    id mockAlertView = [OCMockObject mockForClass:[UIAlertView class]];
    [[[mockAlertView stub] andReturn:mockAlertView] alloc];
    (void)[[[mockAlertView expect] andReturn:mockAlertView]
       initWithTitle:OCMOCK_ANY
       message:OCMOCK_ANY
       delegate:OCMOCK_ANY
       cancelButtonTitle:OCMOCK_ANY
       otherButtonTitles:OCMOCK_ANY, nil];
    [[mockAlertView expect] show];


    //set the actual IBOutlets with login values designed to sucessfully login
    self.controller.usernameTextField.text = @"error";
    self.controller.passwordTextField.text = @"error";

    //call the login process to verify the login method
    [self.controller performLogin:nil];

    [mockAlertView verify];
    [mockAlertView stopMocking];

}

我不记得 OCMock api 了,所以肯定不会编译,但我相信这个想法是正确的。 (我基本都在关注this guide

首先,最好用两个测试来测试这个。 第一个测试将仅检查 [ViewHelper showAlertForTitle:"Error" andTheMessage:@"network error" andAccessibilityLabel:@"Network Error"] 在异常时被调用。第二个测试将检查在调用 +[ViewHelper showAlertForTitle:andTheMessage:andAccessibilityLabel:] 时出现警报视图(或您希望发生的任何其他事情)。

第一个测试的(重要部分):

// ① You need to setup something to raise an exception
// so the catch block will be called
// Probably you want to use [OCMArg any] or [OCMArg anyPointer]
OCMStub([mockServices loginWithUsername:[OCMArg isNotNil]
                            andPassword:[OCMArg isNotNil]])
  .andThrow(anException);

// ② Call your method
// sender can be nil in your code.
[controller performLogin:sender]; 

// ③ Expect your method to be called
OCMExpect([mockViewHelper showAlertForTitle:"Error" 
                              andTheMessage:@"network error"
                      andAccessibilityLabel:@"Network Error"]);

// ④ Verify
OCMVerifyAll(mockViewHelper)

二测要点:

// ① Stub everything you want to check to be called inside 
// `+[ViewHelper showAlertForTitle:andTheMessage:andAccessibilityLabel:]`
// probably your UIAlertView, etc
id mockAlertView = ...

// ② Call your method
[ViewHelper showAlertForTitle:@"something" 
                andTheMessage:@"something" 
        andAccessibilityLabel:@"something"]; 

// ③ Expect your stuff to be called
OCMExpect([mockAlertView someMethod]);

// ④ Verify
OCMVerifyAll(mockAlertView)

最后一件事,在 objc 世界中,异常用于程序员错误,如果您正在设计这些 类 考虑 return 像许多其他 Cocoa API 一样的 NSError。

正如 nacho4d 在回答中提到的,这有两个部分:使登录模型抛出异常并验证控制器是否显示错误。

考虑到您描述的限制条件,我将对登录模型使用部分模拟来抛出异常:

id modelMock = OCMPartialMock([[Services sharedInstance] getLoginModel]);
OCMStub([modelMock doLoginWithUsername:[OCMArg any] andPassword:[OCMArg any]])
   .andThrow([NSException exceptionWithName:@"TestException" reason:@"Test" userInfo:nil]);

为了检查面板是否显示,我不会理会视图。我只是检查助手中的 class 方法是从 performLogin: 实现中调用的:

id viewHelperMock = OCMClassMock([ViewHelper class]);
[controller performLogin:sender]; 
OCMVerify([viewHelperMock showAlertForTitle:"Error" andTheMessage:@"network error" andAccessibilityLabel:@"Network Error"]);