将模拟类注入方法以单元测试方法

5

我正在尝试对一个依赖于另一个类的方法进行单元测试。该方法调用了该类的一个类方法,基本上是这样的:

func myMethod() {

     //do stuff
     TheirClass.someClassMethod()

}
使用依赖注入技术,我希望能够用模拟对象替换掉 "TheirClass",但是我不知道如何实现。是否有一种方法可以传递一个模拟 (而不是实例)? 编辑:感谢回复。也许我应该提供更多细节。我试图模拟的类方法在一个开源库中。 以下是我的方法。我正在尝试测试它,同时模拟对 NXOAuth2Request.performMethod 的调用。这个类方法发出一个网络调用,从我们的后端获取已认证用户的信息。在闭包中,我将此信息保存到开源库提供的全局账户存储中,并发布成功或失败的通知。
func getUserProfileAndVerifyUserIsAuthenticated() {

    //this notification is fired when the refresh token has expired, and as a result, a new access token cannot be obtained
    NSNotificationCenter.defaultCenter().addObserver(self, selector: "didFailToGetAccessTokenNotification", name: NXOAuth2AccountDidFailToGetAccessTokenNotification, object: nil)

    let accounts = self.accountStore.accountsWithAccountType(UserAuthenticationScheme.sharedInstance.accountType) as Array<NXOAuth2Account>
    if accounts.count > 0 {
        let account = accounts[0]

        let userInfoURL = UserAuthenticationScheme.sharedInstance.userInfoURL

        println("getUserProfileAndVerifyUserIsAuthenticated: calling to see if user token is still valid")
        NXOAuth2Request.performMethod("GET", onResource: userInfoURL, usingParameters: nil, withAccount: account, sendProgressHandler: nil, responseHandler: { (response, responseData, error) -> Void in

            if error != nil {
                println("User Info Error: %@", error.localizedDescription);
                NSNotificationCenter.defaultCenter().postNotificationName("UserCouldNotBeAuthenticated", object: self)
            }
            else if let data = responseData {
                var errorPointer: NSError?
                let userInfo = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &errorPointer) as NSDictionary

                println("Retrieved user info")
                account.userData = userInfo

                NSNotificationCenter.defaultCenter().postNotificationName("UserAuthenticated", object: self)
            }
            else {
                println("Unknown error retrieving user info")
                NSNotificationCenter.defaultCenter().postNotificationName("UserCouldNotBeAuthenticated", object: self)
            }
        })
    }
}

1
依赖注入,你的类应该有一个接受 TheirClass 的构造函数。 - Joe
我如何传递一个类而不是类的实例? - Mike Taverne
构造函数应该接受一个实例,其中您将传递模拟对象。 - Joe
我无法使用模拟实例,因为我必须在真实对象上调用的方法是类方法,而不是实例方法。 - Mike Taverne
如果类方法没有全局状态或产生副作用,那么只需专注于对静态方法进行单元测试,您就不需要在myMethod内部测试它。 - Joe
1个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
7
在Swift中,更好的方法是通过传递函数来实现。有很多方法可以实现这一点,但这里介绍一种方法:
func myMethod(completion: () -> Void = TheirClass.someClassMethod) {
    //do stuff
    completion()
}
现在您可以传递一个完成处理程序,而现有的代码将继续使用默认方法。注意您可以引用函数本身(TheirClass.someClassMethod)。您不必将其包装在闭包中。 您可能会发现让调用者始终传递这个比使其成为默认更好。这将使该类与TheirClass的绑定较少,但无论哪种方式都可以。 最好将这种松耦合、设计可测试性的集成到代码中,而不是想出聪明的方法来模拟事物。事实上,您应该问自己myMethod()是否真的应该调用someClassMethod()。也许这些东西应该被拆分,以使它们更容易被测试,然后在更高的级别上将它们组合起来。例如,也许myMethod应该返回一些东西,然后您可以将其传递给someClassMethod(),这样就没有需要担心的状态了。

你好 Rob,我通过寻找类似的解决方案发现了这个方法。在我的情况下,我有一个稍微不同的问题,因为我应该测试的静态方法还接受一个参数,我该如何解决这个问题? - dev_mush
我不明白这个问题。重写该方法以接受一个接受参数的闭包。这是同样的事情。 - Rob Napier
我的问题出在默认值和 obj-c 互操作性上,如果你有时间的话,我在这里提出了一个关于这个问题的问题:https://dev59.com/CFgQ5IYBdhLWcg3w0HSW - dev_mush
可爱的解决方案,但我有一个关于将它们链接在一起的问题。假设我想测试调用myMethod()的hisMethod(),但我想注入一个自定义实现来替换myMethod()。当将新的myMethod作为参数添加到hisMethod时,我必须指定输入参数,但我不能省略具有默认值的参数,即注入到myMethod中的方法。我必须在hisMethod的参数中包括自定义的myMethod和永远不会被调用的注入方法吗?还是我可以定义一个函数参数而不指定默认输入? - Denis Balko

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,