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 :
- I prepare my bodyParams & image for my multipart request.
- I create a listener for listen HTTP response when received.
- And I create my AlamofireHTTPClient (makeSUT function) by passing my listener and call my Alamorefire requestMultipart with my bodyParams & image. Then a execute my client.
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 :).
2
Answers
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
):Now, NOT any
Data
value can be converted into UTF8 String. Some "bits combinaison" don’t have UTF8 value, hence why you can getString(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 benil
.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.
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 theURLRequest
that is performed will have nohttpBody
. Instead, it uses thehttpBodyStream
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.