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

Posted

技术标签:

【中文标题】将模拟类注入方法以单元测试方法【英文标题】:Inject mock class into method to unit test method 【发布时间】:2015-01-23 18:04:07 【问题描述】:

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

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)
            
        )
    

【问题讨论】:

依赖注入,你的类应该有一个接受TheirClass的构造函数。 我如何传递一个类和一个类的实例? 构造函数应该接受一个实例,您将在其中传递模拟对象。 我不能使用模拟实例,因为我必须在真实对象上调用的方法是类方法,而不是实例方法。 如果类方法没有全局状态或产生副作用,那么只需专注于对静态方法进行单元测试,您就不需要在myMethod 内部对其进行测试。 programmers.stackexchange.com/questions/5757/… 【参考方案1】:

在 Swift 中,这最好通过传递一个函数来完成。有很多方法可以解决这个问题,但这里有一个:

func myMethod(completion: () -> Void = TheirClass.someClassMethod) 
    //do stuff
    completion()

现在您可以传递一个完成处理程序,而现有代码将继续使用默认方法。请注意如何引用函数本身 (TheirClass.someClassMethod)。您不必将其包裹在闭包中。

你可能会发现让调用者一直传递它而不是让它成为默认值更好。这将使这个类更少地绑定到TheirClass,但无论哪种方式都可以。

最好将这种松散耦合、可测试性设计集成到代码本身中,而不是想出巧妙的方法来模拟事物。事实上,你应该问问自己myMethod() 是否真的应该调用someClassMethod()。也许这些东西应该被拆分,使它们更容易测试,然后在更高的层次上将它们捆绑在一起。例如,也许myMethod 应该返回一些你可以传递给someClassMethod() 的东西,这样你就不需要担心任何状态。

【讨论】:

你好 Rob,我通过寻找非常相似的东西遇到了这个解决方案,在我的情况下,我有一个稍微不同的问题,因为我应该测试的静态方法也接受一个参数,我该如何工作周围? 我不明白这个问题。重写方法以接受一个接受参数的闭包。是一样的。 我的问题是默认值和 obj-c 互操作性,如果你有时间花点时间,我在这里问了一个问题:***.com/questions/42170690/… 可爱的解决方案,但我有一个关于将这些链接在一起的问题。假设我想测试调用 myMethod() 的 hisMethod(),但我想注入 myMethod() 以替换为自定义实现。在将新的myMethod 作为参数添加到hisMethod 时,我必须指定输入参数,但不能省略具有默认值的参数,即注入到myMethod 中的方法。我必须在 hisMethod 的参数中包含自定义的 myMethod 和 myMethod 注入的方法,它们甚至永远不会被调用吗?或者我可以定义一个函数参数而不用默认值指定输入

以上是关于将模拟类注入方法以单元测试方法的主要内容,如果未能解决你的问题,请参考以下文章

Nestjs 单元测试 - 模拟方法守卫

使用proxyquire和mocha在单元测试中模拟方法调用时如何模拟时间延迟(超时)?

单元测试:如何在流明中模拟具有路由参数的请求

使用 Moq 模拟单元测试的异步方法

您如何在 C# 中模拟文件系统以进行单元测试?

将 AddOrUpdate 与 Entity Framework 6 Repository 一起使用的单元测试方法