菜鸟教程小白 发表于 2022-12-11 20:09:02

ios - Swift 中用于单元测试的静态函数的依赖注入(inject)


                                            <p><p>我知道这看起来是一个常见问题,但是在阅读了 10-15 教程并查看了如何为我的服务类编写测试之后。我无法解决将静态函数移动到协议(protocol)等的问题。用于依赖注入(inject)</p>

<p>我有一个如下图所示的网络层。我所有的函数类(如获取用户、新闻、媒体等)都调用“服务调用者”类,然后如果响应错误;调用“服务错误”类来处理错误,如果没有错误,则解码 JSON。 </p>

<p>我的问题是我将服务类调用为静态函数,如“ServiceCaller.performRequest”,如果出现错误,我还将错误类调用为静态函数,如“ServiceError.handle”。它还调用 URLCache 类来获取请求 url 的路径。我不确定<strong>如何让它们在测试类中进行依赖注入(inject)和模拟。</strong>正如我在教程中发现的那样,我应该这样写;</p>

<pre><code>protocol MyProtocol{
    func myfunction() -&gt; Void
}
class A{
    let testProtocol = MyProtocol!
    init(pro: MyProtocol){
      testProtocol = pro
    }
}
</code></pre>

<p>可能在测试类的设置函数中;</p>

<pre><code>myMockProtocol = ...
myTestclass = A.init(pro: myMockProtocol)
</code></pre>

<p> <a href="/image/HFymQ.png" rel="noreferrer noopener nofollow"><img src="/image/HFymQ.png" alt="How My Network layer works"/></a> </p>

<p>但我找不到如何获得像 ServiceCaller.performRequest 或 ServiceError.handle.. 这样的静态调用; (问题底部的简化版)</p>

<pre><code>class AppInitService{

static func initAppRequest(_ completion: @escaping (_ appInitRecevingModel: Result&lt;AppInitRecevingModel&gt;) -&gt; Void) {

    let sendingModel = AppInitSendingModel(cmsVersion: AppDefaults.instance.getCMSVersion())
    let route = ServiceRouter(method: .post, path: URLCache.instance.getServiceURL(key: URLKeys.initApp), parameters: (sendingModel.getJSONData()), timeoutSec: 1)
    ServiceCaller.performRequest(route: route) { (result) in
      if let error = result.error{
            if let statusCode = result.response?.statusCode{
                completion(.error(ServiceError.handle(error: error, statusCode: statusCode)))
            }else{
                completion(.error(ServiceError.handle(error: error, statusCode: error._code)))
            }
      }else{
            if let data = result.data{
                do{
                  var responseJson = JSON(data)
                  responseJson[&#34;idleTimeoutInMinutes&#34;] = 10
                  let input = try AppInitRecevingModel(data: responseJson.rawData())
                  completion(.success(input))
                }catch let error{
                  completion(.error(ServiceError.handle(error: error, statusCode: -1002)))
                }
            }
      }}
}
}
</code></pre>

<p>我的测试课:</p>

<pre><code>class MyProjectAppInitTests: XCTestCase {

var appInitTest: AppInitService!

override func setUp() {
    super.setUp()
    // Put setup code here. This method is called before the invocation of each test method in the class.
    appInitTest = AppInitService.init()
}

override func tearDown() {
    // Put teardown code here. This method is called after the invocation of each test method in the class.
    appInitTest = nil
    super.tearDown()
}

func testExample() {
    // This is an example of a functional test case.
    // Use XCTAssert and related functions to verify your tests produce the correct results.
    let testParamater = [&#34;string&#34;:&#34;test&#34;]
    let route = ServiceRouter(method: .post, path: &#34;/testPath&#34;, parameters: testParamater.getJSONData(), timeoutSec: 10)
    appInitTest. //cant call anything in here
}
</code></pre>

<p>我寻找的教程单元测试;</p>

<p> <a href="https://www.raywenderlich.com/150073/ios-unit-testing-and-ui-testing-tutorial" rel="noreferrer noopener nofollow">https://www.raywenderlich.com/150073/ios-unit-testing-and-ui-testing-tutorial</a> </p>

<p> <a href="https://www.swiftbysundell.com/posts/time-traveling-in-swift-unit-tests" rel="noreferrer noopener nofollow">https://www.swiftbysundell.com/posts/time-traveling-in-swift-unit-tests</a> </p>

<p> <a href="https://marcosantadev.com/test-doubles-swift" rel="noreferrer noopener nofollow">https://marcosantadev.com/test-doubles-swift</a> </p>

<p> <a href="http://merowing.info/2017/04/using-protocol-compositon-for-dependency-injection/" rel="noreferrer noopener nofollow">http://merowing.info/2017/04/using-protocol-compositon-for-dependency-injection/</a> </p>

<p>编辑:一种解决方案可能是为整个网络层和服务类编写初始化类,然后摆脱静态函数?但我不确定这是否是一个好方法。</p>

<p>编辑 2:简化代码;</p>

<pre><code>class A{

static func b(completion:...){
    let paramater = ObjectModel(somevariable: SomeClass.Singleton.getVariable()) //Data that I sent on network request
    let router = ServiceRouter(somevariable: SomeClassAgain.Singleton.getsomething()) //Router class which gets parameters, http method etc..

    NetworkClass.performNetworkRequest(sender: object2){ (result) in
      //Result - What I want to test (Write UnitTest about)
    }
}
}
</code></pre></p>
                                    <br><hr><h1><strong>Best Answer-推荐答案</ strong></h1><br>
                                            <p><p>使用模拟。</p>

<pre><code>class ServiceCallerMock: ServiceCaller {
      override class func performRequest(route: ServiceRouter) -&gt; (Any?) -&gt; Void? {
            //your implementation goes here
      }
    }
</code></pre>

<p>您可以模拟 <code>ServiceCaller</code> 并覆盖 performRequest 方法,然后将函数更改为:</p>

<pre><code>static func initAppRequest(_ completion: @escaping (_ appInitRecevingModel: Result&lt;AppInitRecevingModel&gt;) -&gt; Void, serviceCaller: ServiceCaller.Type = ServiceCaller.self) {
    ...
    serviceCaller.performRequest(route: route) { (result) in
    ...
}
</code></pre>

<p>然后您可以使用 <code>ServiceCaller</code> 的模拟实现来调用 <code>initAppRequest</code> 函数。</p></p>
                                   
                                                <p style="font-size: 20px;">关于ios - Swift 中用于单元测试的静态函数的依赖注入(inject),我们在Stack Overflow上找到一个类似的问题:
                                                        <a href="https://stackoverflow.com/questions/50251997/" rel="noreferrer noopener nofollow" style="color: red;">
                                                                https://stackoverflow.com/questions/50251997/
                                                        </a>
                                                </p>
                                       
页: [1]
查看完整版本: ios - Swift 中用于单元测试的静态函数的依赖注入(inject)