ios - Swift 中用于单元测试的静态函数的依赖注入
问题描述
我知道这看起来像是一个常见问题,但是在阅读了 10-15 教程并查看了如何为我的服务类编写测试之后。我无法解决将静态函数移动到协议等的问题。用于依赖注入
我有一个如下图所示的网络层。我所有的函数类(如获取用户、新闻、媒体等)都调用“服务调用者”类,然后如果响应错误;调用“服务错误”类来处理错误,如果没有错误,则解码 JSON。
我的问题是我将服务类作为静态函数调用,如“ServiceCaller.performRequest”,如果出现错误,我还将错误类调用为静态函数,如“ServiceError.handle”。它还调用 URLCache 类来获取请求 url 的路径。我不确定如何让它们在测试类中进行依赖注入和模拟。正如我在教程中发现的那样,我应该这样写;
protocol MyProtocol{
func myfunction() -> Void
}
class A{
let testProtocol = MyProtocol!
init(pro: MyProtocol){
testProtocol = pro
}
}
并且可能在测试类的设置功能中;
myMockProtocol = ...
myTestclass = A.init(pro: myMockProtocol)
但我找不到如何获得像 ServiceCaller.performRequest 或 ServiceError.handle 之类的静态调用..;(问题底部的简化版)
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)))
}
}
}}
}
}
我的测试课:
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
}
我寻找的教程单元测试;
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/
编辑:一种解决方案可能为整个网络层和服务类编写初始化类,然后摆脱静态函数?但我不确定这是否是一个好方法。
编辑 2:简化代码;
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
.
推荐阅读
- spring - Spring Boot,数据库弹性
- python - / 不支持的操作数类型:'str' 和 'float' Python
- terraform - terraform 状态刷新并接受更改,不再使用旧代码更改
- quanteda - 阅读连字符的 Quanteda 版本更改
- reactjs - 未调用 componentDidUpdate 函数
- android - Android TextInputLayout - 底线下方没有空格
- firebase - 有没有办法在崩溃后的第一个应用启动时安排 Firebase 应用内消息?
- flutter - Flutter - 我正在寻找一种方法来创建一个圆圈,图标均匀分布在上面
- sql - 从多列返回值的 SQL 函数
- python - Pymongo做匹配项目查询