如何在测试中覆盖Swift的类属性

4
我有一个Swift类需要进行单元测试。该类的主要目的是发起HTTP调用。我刚刚使用Mockingjay模拟了所有网络请求,但我希望确保未来的请求也能被模拟。在进行最初的模拟时,我将正在使用的基本URL替换为一个不起作用的URL,但我只想在测试中保留它。被测试的类看起来像这样:
public class MyWebServiceWrapper {
    ...

    public class var baseURL: String {
        return "https://api.thesite.com"
    }

    ...
}

我尝试过在Objective-C类中使用OCMock来替换baseURL的实现,也尝试过使用方法交换NSHipster上描述的(我让该类从NSObject派生并用方法替换了上面显示的属性而不是使用属性 - 我宁愿不进行方法交换,特别是因为我不需要该类成为NSObject子类)。
这是我用于执行尝试交换的代码,但没有任何效果。对于下面的两个尝试解决方案,我像下面这样重写了我的测试类(因为看起来两种方法都不能很好地与Swift属性或纯Swift类一起使用):
@objc public class MyWebServiceWrapper: NSObject {
    ...

    public class var baseURL: String {
        return baseURLValue()
    }

    public class func baseURLValue() -> String {
        return "https://api.thesite.com"
    }

    ...
}

这是我的交换尝试(使用Swift)。根据日志,在每次情况下,我都会触发else语句,以帮助弄清楚发生了什么。
extension MyWebServiceWrapper {

    public override class func initialize() {
        struct Static {
            static var token: dispatch_once_t = 0
        }

        if self !== MyWebServiceWrapper.self {
            return
        }

        dispatch_once(&Static.token) {
            NSLog("Replacing MyWebServiceWrapper base URL...")
            let originalSelector = #selector(baseURLValue)
            let swizzledSelector = #selector(networkFreeBaseURL)

            guard let klass = object_getClass(self) else {
                return
            }

            let originalMethod = class_getClassMethod(klass, originalSelector)
            let swizzledMethod = class_getClassMethod(klass, swizzledSelector)

            let didAddMethod = class_addMethod(klass, originalSelector,
                                               method_getImplementation(swizzledMethod),
                                               method_getTypeEncoding(swizzledMethod))

            if didAddMethod {
                NSLog("MyWebServiceWrapper method added")
                class_replaceMethod(klass, swizzledSelector, method_getImplementation(originalMethod),
                                    method_getTypeEncoding(originalMethod))
            } else {
                method_exchangeImplementations(originalMethod, swizzledMethod)
                NSLog("MyWebServiceWrapper implementation replaced")
            }
        }
    }

    class func networkFreeBaseURL() -> String {
        return "https://tests-shouldnt-hit-the-network.com"
    }

}

这是我尝试使用OCMock(在Objective-C中)替换方法的方式。该代码在配置我的单元测试期间调用。

id wrapperMock = OCMClassMock([MyWebServiceWrapper class]);
OCMStub(ClassMethod([wrapperMock baseURLValue])).andReturn(@"https://tests-shouldnt-hit-the-network.com");

如何正确实现我的目标,即从baseURL返回无效值?


你需要找到另一种方法,否则这只是一个变通办法 :( 你找到任何解决方案了吗? - denis631
1
@denis631 我仍在使用我在下面提供的答案。 - Dov
难过...这似乎不太对,但你知道...GTD:做事情 :) - denis631
1个回答

1
我对它不是完全满意(换句话说,我仍然希望有替代答案),因为它需要修改被测试类的代码,但这就是我最终得出的结果。它完成了工作,并且不能轻易地被非测试代码滥用,因为我将覆盖变量设为internal
public class MyWebServiceWrapper {
    ...

    /// Only use for testing!
    internal static var baseURLOverride: String?

    public class var baseURL: String {
        return baseURLOverride ?? "https://api.thesite.com"
    }

    ...
}

并且从我的测试配置中:

@testable
import MyFramework

...

// Inside test configuration
MyWebServiceWrapper.baseURLOverride = "https://tests-shouldnt-hit-the-network.com"

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