我相信我对新版本的 Alamofire 有一个解决方案。我的 Swift 和 DI 技能有点菜鸟,所以这可能可以改进,但我想我会分享。模拟 Alamofire 最具挑战性的部分是模拟网络调用中的方法链接(request().responseJSON)。
网络电话:
let networkManager: NetworkManagerProtocol!
init(_ networkManager: NetworkManagerProtocol = NetworkManagerTest(SessionManager())) {
self.networkManager = networkManager
}
func create(_ params: [String: Any], completion: @escaping (Response<Success,Fail>) -> Void) {
self.networkManager.manager.request(self.url!, method: .post, parameters: params, encoding: URLEncoding.default, headers: nil).responseJSON {
response in
if response.result.isSuccess {
completion(Success())
} else {
completion(Fail())
}
}
}
您将注入网络调用类的管理器:NetworkManagerProtocol 为get manager
各种类型的网络管理器提供功能。
class NetworkManager: NetworkManagerProtocol {
private let sessionManager: NetworkManagerProtocol
init(_ sessionManager: NetworkManagerProtocol) {
self.sessionManager = sessionManager
}
var manager: SessionManagerProtocol {
get {
return sessionManager.manager
}
set {}
}
}
扩展 Alamofire 的 SessionManager 类:
这是我们向 SessionManager 添加协议和自定义功能的地方。请注意,协议的 request 方法是 Alamofire 的 request method 的包装器。
extension SessionManager: NetworkManagerProtocol, SessionManagerProtocol {
private static var _manager = SessionManager()
var manager: SessionManagerProtocol {
get {
return SessionManager._manager
}
set {
let configuration = URLSessionConfiguration.default
SessionManager._manager = Alamofire.SessionManager(configuration: configuration, delegate: SessionManager.default.delegate)
}
}
func request(_ url: URLConvertible, method: HTTPMethod, parameters: Parameters, encoding: ParameterEncoding, headers: HTTPHeaders?) -> DataRequestProtocol {
let dataRequest: DataRequest = self.request(url, method: method, parameters: parameters, encoding: encoding, headers: headers)
return dataRequest
}
}
为 mock api 调用创建一个 SessionManagerMock:
该类创建一个 SessionManagerMock 对象,然后使用其请求方法检索模拟数据。
class SessionManagerMock: NetworkManagerProtocol, SessionManagerProtocol {
private static var _manager = SessionManagerMock()
var manager: SessionManagerProtocol {
get {
return SessionManagerMock._manager
}
set {}
}
func request(_ url: URLConvertible, method: HTTPMethod, parameters: Parameters, encoding: ParameterEncoding, headers: HTTPHeaders?) -> DataRequestProtocol {
return DataRequestMock()
}
}
扩展 Alamofire 的 DataRequest 类:
同样,请注意协议的 responseJSON 类是 DataRequests 的 responseJSON 类的包装器。
extension DataRequest: DataRequestProtocol {
func responseJSON(completionHandler: @escaping (DataResponse<Any>) -> Void) -> Self {
return self.responseJSON(queue: nil, options: .allowFragments, completionHandler: completionHandler)
}
}
DataRequestMock 类:
该类存储模拟请求的数据。它可以再扩展一点(添加请求数据等),但你明白了。
class DataRequestMock: DataRequestProtocol {
static var statusCode: Int = 200
var dataResponse = DataResponse<Any>(
request: nil,
response: HTTPURLResponse(url: URL(string: "foo.baz.com")!, statusCode: DataRequestMock.statusCode, httpVersion: "1.1", headerFields: nil),
data: nil,
result: Result.success(true), // enum
timeline: Timeline()
)
func response(completionHandler: @escaping (DataResponse<Any>) -> Void) -> Self {
completionHandler(dataResponse)
return self
}
func responseJSON(completionHandler: @escaping (DataResponse<Any>) -> Void) -> Self {
return response(completionHandler: completionHandler)
}
}
协议机器人:
protocol NetworkManagerProtocol {
var manager: SessionManagerProtocol { get set }
}
protocol SessionManagerProtocol {
func request(_ url: URLConvertible, method: HTTPMethod, parameters: Parameters, encoding: ParameterEncoding, headers: HTTPHeaders?) -> DataRequestProtocol
}
protocol DataRequestProtocol {
func responseJSON(completionHandler: @escaping (DataResponse<Any>) -> Void) -> Self
}
测试方法:
可以进行很多改进以使其更具动态性,但您再次明白了
var sut: UserService?
override func setUp() {
super.setUp()
sut = UserService(NetworkManagerTest(SessionManagerMock()))
}
func testCreateUser201() {
DataRequestMock.statusCode = 201
let params : [String : String] = ["name": "foo baz", "email": "foobaz@gmail.com", "password": "tester123"]
var resultCode: Int!
sut?.create(params) {(response: Response) in
switch response {
case .success(let resp):
resultCode = resp.statusCode
case .failure(let resp):
resultCode = resp.statusCode
}
}
XCTAssertEqual(resultCode, 201, "Status code is wrong")
}