How to access the raw content from a response in Vapor 3 unit test? - swift

I'm coming from using tooling such as SuperTest with NodeJS and looking for relevant equivalents to support testing with Vapor 3 and server side swift.
I see a pattern of using making a testable application with Vapor 3 to do testing of endpoints, examples being https://github.com/raywenderlich/vapor-til/blob/master/Tests/AppTests/Application%2BTestable.swift and the write-up at https://medium.com/swift2go/vapor-3-series-iii-testing-b192be079c9e.
When using these in tests, the format generally looks something like:
func testGettingASingleUserFromTheAPI() throws {
let user = try User.create(name: usersName, username: usersUsername, on: conn)
let receivedUser = try app.getResponse(to: "\(usersURI)\(user.id!)", decodeTo: User.Public.self)
XCTAssertEqual(receivedUser.name, usersName)
XCTAssertEqual(receivedUser.username, usersUsername)
XCTAssertEqual(receivedUser.id, user.id)
}
(from Vapor-TIL example code)
In all of these examples, the return values are really set to be handed back to something decodable (the decodeTo: kind of setup). In some cases in my Vapor 3 code, I want to just validate some non-JSON encoded results - just simple strings, and validate the results - but I've not found the methods to get into the content or convenient ways to validate it with XCTAssert.
response.content is available, a container around the overall response (of type ContentContainer). Are there some examples or good ways at getting to the underlying content representation to validate them directly?

You could write your own additional methods in Application+Testable like
func getRawResponse(to path: String) throws -> Response {
return try self.sendRequest(to: path, method: .GET)
}
func getStringResponse(to path: String) throws -> String {
let response = try self.getRawResponse(to: path)
guard let data = response.http.body.data,
let string = String(data: data, encoding: .utf8) else {
throw SomeError("Unable to decode response data into String")
}
return string
}
and then call them to get either raw Response or decoded String like
func testGettingHelloWorldStringFromTheAPI() throws {
let string = try app. getStringResponse(to: "some/endpoint")
XCTAssertEqual(string, "Hello world")
}

Related

Code improvement - how to serve use pictures in Vapor?

I have a working directory that contains every user's picture and I am trying to implement a call that returns data containing the user's picture, defined in this structure:
struct ImageData: Content {
var picture: Data // UIImage data
}
I tried to implement a solution also partially using what I found in the book 'Server Side Swift with Vapor' (version 3) in chapter 26 but that's different for me because I am not using Leaf and I need to return the data directly.
I came up with this function to return the user picture, which does its job but I am trying to improve it.
func getProfilePictureHandler(_ req: Request) throws -> EventLoopFuture<ImageData> {
return User.find(req.parameters.get("userID"), on: req.db)
.unwrap(or: Abort(.notFound))
.flatMap { user in
// To do: throw error (flatMapThrowing?)
let filename = user.profilePicture!
let path = req.application.directory.workingDirectory
+ imageFolder
+ filename
// Improvement: Do I need this?
var data = Data()
return req.fileio.readFile(at: path) { buffer -> EventLoopFuture<Void> in
let additionalData = Data(buffer: buffer)
data.append(contentsOf: additionalData)
return req.eventLoop.makeSucceededVoidFuture()
}.map {
return ImageData(picture: data)
}
}
}
First:
How to implement this using flatMapThrowing? If I replace flatMap with flatMapThrowing I get this error: "Cannot convert return expression of type 'EventLoopFuture' to return type 'ImageData'". Which doesn't make sense to me considering that flatMap allows returning a future and not a value.
I didn't find any solution other than using a Data variable and appending chunks of data as more data is read. I am not sure that this is thread-safe, FIFO and I don't consider it an elegant solution. Does anybody know any better way of doing it?
The short answer is that as soon as you have the file path, Vapor can handle it all for you:
func getProfilePictureHandler(_ req: Request) throws -> EventLoopFuture<Response> {
return User.find(req.parameters.get("userID"), on: req.db)
.unwrap(or: Abort(.notFound))
.tryflatMap { user in
// To do: throw error (flatMapThrowing?)
guard let filename = user.profilePicture else {
throw Abort(.notFound)
}
let path = req.application.directory.workingDirectory
+ imageFolder
+ filename
return req.fileio.streamFile(at: path)
}
}
You can use tryFlatMap to have a flatMap that can throw and you want to return a Response. Manually messing around with Data is not usually a good idea.
However, the better answers are use async/await and the FileMiddleware as two tools to clean up your code and remove the handler altogether

How do I fetch HTML of URL using Vapor Swift?

In one of my routes, I want to fetch HTML from a different site. https://docs.vapor.codes/4.0/content/ documents support for JSON and such but I couldn't find anything on raw HTML.
request.client.get(URI(string: "https://example.com/")).map { (response: ClientResponse) -> String? in
if response.status == .ok && response.content.contentType == .html {
return response.content... // How do I get raw html?
}
return nil
}
How do I get the raw HTML from the client response?
There are a couple of ways you can do this, depending on which String initializer you happen to fancy. Note that to use any of the following methods, you will need to unwrap the response body first:
guard let body = response.body else {
throw Abort(.internalServerError)
}
The first way is using the ByteBuffer.readString method.
guard let html = body.readString(length: body.readableBytes) else {
throw Abort(.internalServerError)
}
Another way is to use the String(decoding:as:) initializer, which can be used to convert any collection of UInt8 integers to a String:
let html = String(decoding: body.readableBytesView, as: UTF8.self)
Anf finally, you can use the String(buffer:) initializer that #iMike suggested.
let html = String(buffer: body)
Keep in mind that the .readBytes method will increment the .readIndex of the ByteBuffer, while the String initializers won't. Though I imagine that this doesn't really matter in your case.

Swift program never enters CompletionHandler for a dataTask

I am in the process of implementing a REST API with Swift. Of course, part of this API is using HTTP requests to retrieve and send data.
Full disclosure, I am inexperienced with Swift and am using this as a learning project to get my feet wet, so to speak. But it's turned into much more of a difficult project than I anticipated.
In implementing the first get method, I have (finally) gotten rid of all the compilation errors. However, when I call the function which utilizes the URLRequest, URLSession, dataTask, etc, it is never entered.
Upon debugging the program, I can watch the program execution reach the CompletionHandler, and skip over it right to "task.resume()."
A similar construction works in a Swift Playground, but does not work in the actual project proper.
So far I have tried a few things, namely making the function access a class instance variable, in hopes that that would force it to execute. But it does not.
I think the issue may be dealing with synchronicity, and perhaps I need to use a Semaphore, but I want to make sure I'm not missing anything obvious first.
import Foundation
/**
A class to wrap all GET and POST requests, to avoid the necessity of repeatedly writing request code in each API method.
*/
class BasicRequest {
private var url: URL
private var header: [String: String]
private var responseType: String
private var jsonResponse: Any?
init(url: URL, header: [String: String], responseType: String) {
self.url = url
self.header = header
self.responseType = responseType
} //END INIT
public func requestJSON() -> Any {
// Create the URLRequest object, and fill the header with the header fields as provided.
var urlRequest = URLRequest(url: self.url)
for (value, key) in self.header {
urlRequest.addValue(value, forHTTPHeaderField: key)
}
let task = URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
print("Entered the completion handler")
if error != nil {
return
}
guard let httpResponse = response as? HTTPURLResponse, 200 == httpResponse.statusCode else {
print("HTTP Request unsuccessful")
return
}
guard let mime = response?.mimeType, mime == "application/json" else {
print("Not a JSON response")
return
}
do {
let json = try JSONSerialization.jsonObject(with: data!, options: [])
print(json)
self.jsonResponse = json
} catch {
print("Could not transform to JSON")
return
}
}
task.resume()
return "Function has returned"
} //END REQUESTJSON
}
The expected result would be returning a JSON object, however that does not seem to be the case.
With respect to error messages, I get none. The only log I get in the debugger is the boilerplate "process exited with code 0."
To be truthful, I'm at a loss with what is causing this not to work.
It appears you're writing this in a command-line app. In that case the program is terminating before the URLRequest completes.
I think the issue may be dealing with synchronicity, and perhaps I need to use a Semaphore, but I want to make sure I'm not missing anything obvious first.
Exactly.
The typical tool in Swift is DispatchGroup, which is just a higher-level kind of semaphore. Call dispatchGroup.enter() before starting the request, and all dispatchGroup.leave() at the end of the completion handler. In your calling code, include dispatchGroup.wait() to wait for it. (If that's not clear, I can add code for it, but there are also a lot of SO answers you can find that will demonstrate it.)

Alamofire XML request to PropertyList

I am trying to parse an XML data using Codable from the sample https://www.w3schools.com/xml/note.xml.
My struct is
struct Note: Codable {
var to: String?
var from: String?
var heading: String?
var body: String?
}
However if I make the following request I get the error responseSerializationFailed : ResponseSerializationFailureReason "PropertyList could not be serialized because of error:\nThe data couldn’t be read because it isn’t in the correct format."
let url = URL(string: "https://www.w3schools.com/xml/note.xml")
Alamofire.request(url!, method: .get, encoding: PropertyListEncoding.default).responsePropertyList { (response) in
guard response.error == nil else {
print(response.error!)
exp.fulfill()
return
}
print(response)
if let data = response.data {
print(data)
let decoder = PropertyListDecoder()
let note = try! decoder.decode(Note.self, from: data)
print(note)
}
}
How do you exactly work with the responsePropertyList in Alamofire?
Currently, Apple's Codable protocol does not have a way to decode XML. While a Plist is XML, XML is not necessarily a Plist unless it follows a certain format.
While there are plenty of third party libraries, I would suggest you take a look at the XMLParsing library. This library contains a XMLDecoder and a XMLEncoder that uses Apple's own Codable protocol, and is based on Apple's JSONEncoder/JSONDecoder with changes to fit the XML standard.
Link: https://github.com/ShawnMoore/XMLParsing
W3School's XML To Parse:
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>
Swift Struct conforming to Codable:
struct Note: Codable {
var to: String
var from: String
var heading: String
var body: String
}
XMLDecoder:
let data = Data(forResource: "note", withExtension: "xml") else { return nil }
let decoder = XMLDecoder()
do {
let note = try decoder.decode(Note.self, from: data)
} catch {
print(error)
}
XMLEncoder:
let encoder = XMLEncoder()
do {
let data = try encoder.encode(self, withRootKey: "note")
print(String(data: data, encoding: .utf8))
} catch {
print(error)
}
There are a number of benefits for using Apple's Codable protocol over that of a third-party's protocol. Take for example if Apple decides to begin supporting XML, you would not have to refactor.
For a full list of examples of this library, see the Sample XML folder in the repository.
There are a few differences between Apple's Decoders and Encoders to fit the XML standard. These are as follows:
Differences between XMLDecoder and JSONDecoder
XMLDecoder.DateDecodingStrategy has an extra case titled keyFormatted. This case takes a closure that gives you a CodingKey, and it is up to you to provide the correct DateFormatter for the provided key. This is simply a convenience case on the DateDecodingStrategy of JSONDecoder.
XMLDecoder.DataDecodingStrategy has an extra case titled keyFormatted. This case takes a closure that gives you a CodingKey, and it is up to you to provide the correct data or nil for the provided key. This is simply a convenience case on the DataDecodingStrategy of JSONDecoder.
If the object conforming to the Codable protocol has an array, and the XML being parsed does not contain the array element, XMLDecoder will assign an empty array to the attribute. This is because the XML standard says if the XML does not contain the attribute, that could mean that there are zero of those elements.
Differences between XMLEncoder and JSONEncoder
Contains an option called StringEncodingStrategy, this enum has two options, deferredToString and cdata. The deferredToString option is default and will encode strings as simple strings. If cdata is selected, all strings will be encoded as CData.
The encode function takes in two additional parameters than JSONEncoder does. The first additional parameter in the function is a RootKey string that will have the entire XML wrapped in an element named that key. This parameter is required. The second parameter is an XMLHeader, which is an optional parameter that can take the version, encoding strategy and standalone status, if you want to include this information in the encoded xml.
PropertyList files although are in XML format, they need to follow Apple's PropertyList DTD: http://www.apple.com/DTDs/PropertyList-1.0.dtd
If you want to map a regular XML file (that do not follow PropertyList DTD) into a model object and you don't mind using an external library you can try XMLMapper.
You model for this XML should look like this:
class Note: XMLMappable {
var nodeName: String!
var to: String?
var from: String?
var heading: String?
var body: String?
required init(map: XMLMap) { }
func mapping(map: XMLMap) {
to <- map["to"]
from <- map["from"]
heading <- map["heading"]
body <- map["body"]
}
}
And you can map it from string using XMLMapper:
let note = XMLMapper<Note>().map(XMLString: xmlString)
Or if you install Requests subspec you can use responseXMLObject(queue:keyPath:mapToObject:completionHandler:) function like:
let url = URL(string: "https://www.w3schools.com/xml/note.xml")
Alamofire.request(url!, method: .get, encoding: XMLEncoding.default).responseXMLObject { (response: DataResponse<Note>) in
let note = response.result.value
print(note?.from ?? "nil")
}
Hope this helps.

How to Get Test Coverage for Guard Statement Fall-through

I started writing iOS unit tests today with the BDD approach. I have a question regarding guard statements and getting to 100% code coverage.
I have the following code, which handles the conversion of Data into Customer objects.
internal final class func customer(from data: Data) -> Customer? {
do {
guard let jsonDictionary = try JSONSerialization.jsonObject(with: data, options: []) as? Dictionary<String, Any> else {
return nil
}
var customerFirstName: String? = nil
var customerLastName: String
if let firstName = jsonDictionary["first_name"] as? String {
customerFirstName = firstName
}
guard let lastName = jsonDictionary["last_name"] as? String else {
return nil
}
customerLastName = lastName
return Customer(firstName: customerFirstName, lastName: customerLastName)
} catch {
return nil
}
}
When our backend was created, some customers were given just a last name, which contained their first and last names. That is why the customer's first name is optional; their full name may be the value for last_name.
In my code, the customer's first name is optional while their last name is required. If their last name is not returned in the received JSON from a network request, then I do not create the customer. Also, if the Data cannot be serialized into a Dictionary, then the customer is not created.
I have two JSON files, both of which contain customer information that I am using to test both scenarios.
One contains no first name in the JSON:
{
"first_name": null,
"last_name": "Test Name",
}
The other contains a first name in the JSON:
{
"first_name": "Test",
"last_name": "Name",
}
In my unit test, using Quick and Nimble, I handle the creation of a Customer when the first name is not available and when it is:
override func spec() {
super.spec()
let bundle = Bundle(for: type(of: self))
describe("customer") {
context("whenAllDataAvailable") {
it("createsSuccessfully") {
let path = bundle.path(forResource: "CustomerValidFullName", ofType: "json", inDirectory: "ResponseStubs")!
let url = URL(fileURLWithPath: path)
let data = try! Data(contentsOf: url)
let customer = DataTransformer.customer(from: data)
expect(customer).toNot(beNil())
}
}
context("whenMissingLastName") {
it("createsUnsuccessfully") {
let path = bundle.path(forResource: "CustomerMissingLastName", ofType: "json", inDirectory: "ResponseStubs")!
let url = URL(fileURLWithPath: path)
let data = try! Data(contentsOf: url)
let customer = DataTransformer.customer(from: data)
expect(customer).to(beNil())
}
}
}
}
This ensures that I am creating a Customer when the first name is missing or present in the returned JSON.
How can I get to 100% code coverage of this method, using BDD, when my code does not hit the else clauses of the guard statements since the data is able to be turned into valid JSON objects? Should I just add another .json file with data that cannot be transformed into a JSON object to ensure that a Customer is not created as well as a .json file that contains a missing last_name to ensure that a Customer is not created?
Am I just over-thinking the "100% code coverage" concept? Do I even need to have the else clauses of the guard statements tested? Do I even have the appropriate approach using the BDD method?
Just write whatever JSON you want — malformed in every way you can think of. Examples:
You can hit your exception-handling with something that isn't correct JSON.
You can hit your very first guard with something that is a JSON array, not a dictionary.
As the saying goes, you only need to cover code that you want to be correct. 😉
TDD and BDD are related. In TDD, you'd write a failing test first. Then, you'd write code that passes that test as quickly as you can. Finally, you'd clean up your code to make it better. It looks like you're adding tests after-the-fact.
By the way, your tests would be much clearer if you didn't use external files, but put the JSON straight into your tests. Here's a screencast showing how I TDD the beginnings of JSON conversion. The screencast is in Objective-C but the principles are the same: https://qualitycoding.org/tdd-json-parsing/
100% code coverage with if let.
At times, its not feasible to forcefully prepare a malformed object to enforce the execution hitting return or return nil used in guard statements.
This is the case when you have 3rd party SDKs integrated and respective 3rd party objects get created runtime within the method.
For example :
func aMethod() {
guard let response = 3rdPartyResponse<3rdPartyInput>.createControl(with: .create(with: .someCase)) as? 3rdPartyResponse<3rdPartyInput> else { return }
}
In this case, its very hard, at times not possible to hit the return.
But If code coverage is the main criteria, you can go with using an if let for such cases If lets does give 100% code coverage