Adding a unit test to your project
Now you're ready to add your own unit tests to the Vapor 3 project you created in the last chapter. Previously, you added a new route called "student" and your web application allows a user to query for a student's record that was stored in the array studentRecords. You can add a unit test to AppTests.swift and check if this feature works as expected:
@testable import App // [1]
import XCTest
import Vapor // [2]
final class AppTests: XCTestCase {
func testNothing() throws {
// add your tests here
XCTAssert(true)
}
func testStudent() throws { // [3]
let myApp = try app(Environment.testing) // [4]
let studentRecords = [ // [5]
"Peter" : 3.42,
"Thomas" : 2.98,
"Jane" : 3.91,
"Ryan" : 4.00,
"Kyle" : 4.00
]
for (studentName, gpa) in studentRecords { // [6]
let query = "/student/" + studentName;
let request = Request(http: HTTPRequest(method: .GET,
url: URL(string: query)!),
using: myApp) // [7]
// [8]
let response = try myApp.make(Responder.self).respond(to: request).wait()
guard let data = response.http.body.data else { // [9]
XCTFail("No data in response")
return
}
let expectedResponse = "The student \(studentName)'s GPA is \(gpa)"
// [10]
if let responseString = String(data: data, encoding: .utf8) {
XCTAssertEqual(responseString, expectedResponse)
}
}
}
static let allTests = [
("testNothing", testNothing),
("testStudent", testStudent)
]
}
The following steps are used to unit test the /student route:
- Add the @testable attribute to the import statement for a high level of access
- Import the Vapor framework
- Add the testStudent() test function
- Create an app instance by calling the app() function
- Copy studentRecords[] from the routes() function in routes.swift
- Use a for loop to make queries for all students in studentRecords[]
- Construct an HTTP request
- Make the HTTP request and retrieve an HTTP response
- Unwrap the data optional of the HTTP response
- Assert the string of the HTTP response data to be the expected string
The @testable attribute in [1] is to elevate the access to the App module. Swift prevents an external entity from accessing anything declared as internal in a compiled module. If you elevate the access level to "public" for testability, it's going to reduce the benefits of type safety. Swift provides an alternative way to work around the access control problem of a module, if you enable the testability (use the -enable-testing compilation flag or set Build Settings | Enable Testability in Xcode) during its compilation. You'll be able to elevate the access when you add the @testable attribute to an import statement for the testing enabled module.
The implementation of a new test in [3] is straightforward. It illustrates the essential steps in making an HTTP request and handling its HTTP response. First, a new instance of app is created in [4] by calling the app()function in app.swift. Next, you copy the studentRecords[] array from routes.swift you used in the previous chapter and iterate each entry of the array for an HTTP request to the Vapor application. Since the request takes a definite time, you use the wait()function in [8] to make sure that you have received a valid HTTP response. In [10], the data of the HTTP response is then compared with the expected response.