简体   繁体   中英

Dependency Injection for Static Functions for Unit Test in Swift

I know this looks like a common question but after reading 10-15 tutorial and looking how can I write test for my service class. I can't solve moving static functions to protocol or etc.. for dependency injection

I have a network layer like below image. All my function classes (like fetch users, news, media etc..) calls "Service Caller" class and after that If response is error; calls "Service Error" class to handle error and If not error, decode the JSON.

My problem is that I'm calling service class as a static function like "ServiceCaller.performRequest" and If It gets error I'm also calling error class as static like "ServiceError.handle". Also It calls URLCache class to get path of request url. I'm not sure how can I make them dependency inject and mock in test class. As I find in tutorials, I should write it like;

protocol MyProtocol{
    func myfunction() -> Void
}
class A{
    let testProtocol = MyProtocol!
    init(pro: MyProtocol){
        testProtocol = pro
    }
}

and in setup function in test class it probably;

myMockProtocol = ...
myTestclass = A.init(pro: myMockProtocol)

我的网络层如何工作

but I can't find how can I get ride of static calls like ServiceCaller.performRequest or ServiceError.handle..; (Simplified version in the bottom part of question)

class AppInitService{

static func initAppRequest(_ completion: @escaping (_ appInitRecevingModel: Result<AppInitRecevingModel>) -> 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["idleTimeoutInMinutes"] = 10
                    let input = try AppInitRecevingModel(data: responseJson.rawData())
                    completion(.success(input))
                }catch let error{
                    completion(.error(ServiceError.handle(error: error, statusCode: -1002)))
                }
            }
        }}
}
 }

My Test class:

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 = ["string":"test"]
    let route = ServiceRouter(method: .post, path: "/testPath", parameters: testParamater.getJSONData(), timeoutSec: 10)
    appInitTest. //cant call anything in here
}

Tutorials I looked for Unit Test;

https://www.raywenderlich.com/150073/ios-unit-testing-and-ui-testing-tutorial

https://www.swiftbysundell.com/posts/time-traveling-in-swift-unit-tests

https://marcosantadev.com/test-doubles-swift

http://merowing.info/2017/04/using-protocol-compositon-for-dependency-injection/

EDIT: One solution maybe writing init class for whole network layer and service classes then get rid of static functions? But I'm not sure If It will be a good approach.

EDIT 2: Simplified 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)
    }
}
}

Use mocking.

class ServiceCallerMock: ServiceCaller {
        override class func performRequest(route: ServiceRouter) -> (Any?) -> Void? {
            //your implementation goes here
        }
    }

You could mock ServiceCaller and override the performRequest method, then change the function to:

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

Then you could call the initAppRequest function using your mock implementation of ServiceCaller .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM