Reputation: 1
I would like to know how can I test multipart sending params with Alamofire (network stack). Ex: send a string with an image (Data type).
My issue is that when I receive a response, I get the URLRequest and I check the httpBody for getting my params. Unfortunately it is nil and I don’t know another way to get multipartData.
(I’ve already do some search before asking here ;))
For Doing this, I create a stub URLProtocol (called URLProtocolStub)
final class URLProtocolStub: URLProtocol {
private struct Stub {
let data: Data?
let response: URLResponse?
let error: Error?
let requestObserver: ((URLRequest) -> Void)?
}
private static var _stub: Stub?
private static var stub: Stub? {
get { return queue.sync { _stub } }
set { queue.sync { _stub = newValue } }
}
private static let queue = DispatchQueue(label: "URLProtocolStub.queue")
static func stub(data: Data?, response: URLResponse?, error: Error?) {
stub = Stub(data: data, response: response, error: error, requestObserver: nil)
}
static func observeRequests(observer: @escaping (URLRequest) -> Void) {
stub = Stub(data: nil, response: nil, error: nil, requestObserver: observer)
}
override class func canInit(with request: URLRequest) -> Bool {
return true
}
override class func canonicalRequest(for request: URLRequest) -> URLRequest {
return request
}
override func startLoading() {
guard let stub = URLProtocolStub.stub else { return }
if let data = stub.data {
client?.urlProtocol(self, didLoad: data)
}
if let response = stub.response {
client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
}
if let error = stub.error {
client?.urlProtocol(self, didFailWithError: error)
} else {
client?.urlProtocolDidFinishLoading(self)
}
stub.requestObserver?(request)
}
}
And I set it on the URLConfigurationSession for use a fake session on Alamaofire.
let configuration = URLSessionConfiguration.af.ephemeral
configuration.protocolClasses = [URLProtocolStub.self]
Finally, my test function for testing HTTP request with multipart data.
func test_uploadMultipart_with_params() async {
// 1
let httpURL = HTTPURLResponse(statusCode: 204)
let bodyParams = ["firstname": "mock", "lastname": "test"]
let imageData = UIColor.yellow.image(CGSize(width: 128, height: 128)).jpegData(compressionQuality: 0.7)
URLProtocolStub.stub(data: nil, response: httpURL, error: nil)
let exp = expectation(description: "Waiting to receive response")
// 2
let loggerDelegate = StubHTTPLoggerDelegate(didReceivedResponse: { request in
XCTAssertEqual(request?.httpBody.flatMap { String(data: $0, encoding: .utf8) }, "firstname=mock&lastname=test")
exp.fulfill()
})
// 3
let result = await makeSUT(loggerDelegate: loggerDelegate).requestMultipart(imageDatas: [imageData], pathType: anyPathType(.post, bodyParameters: bodyParams, urlParameters: nil))
do { try result.get() } catch { XCTFail("\(error)") }
wait(for: [exp], timeout: 1.0)
}
I explain this function step by step :
My test is checking if I am sending the right parameters on a Alamofire multipart request. This test failed because the httpBody from a request (URLRequest) is nil and I don't know how to get my multipart params to test it :(.
Thanks for helping me :).
Upvotes: 0
Views: 84
Reputation: 12770
Without seeing the underlying Alamofire call I can't be sure but Alamofire's multipart uploads use a URLSessionUploadTask
from a file or memory, so the URLRequest
that is performed will have no httpBody
. Instead, it uses the httpBodyStream
to stream the data from disk or memory, so you'll need to read that data to do your parsing and comparisons, taking into account the issues @Larme mentioned.
Upvotes: 1
Reputation: 26036
In multiform part data, the body is more or less a big data that can be separated into multiple subdata (that if we "split them": Data1+Data2+Data3...+DataN
):
Data1 //Boundary
Data2 //SomeParam1
Data3 //Boundary
Data4 //SomeParam2
Data5 //Boundary
...
Now, NOT any Data
value can be converted into UTF8 String. Some "bits combinaison" don't have UTF8 value, hence why you can get String(data: someData, encoding: .utf8)
that can be nil.
And you can try it yourself since you have an image:
print("Image to Str: \(String(data: imageData, encoding: .utf8))")
and it would be nil
.
So if you are trying to convert your big data into a String, it won't work since some part in the middle is the image and will be nil.
So in reality, you should split your body and retrieve each part. The boundary should be in the Header so you should be able to retrieve it. , There are also usually a lot of "\n" and "\r" and "-" as separators. Then, you have to iterate and find the desired value.
Upvotes: 0