Skip to content

Commit 0071b73

Browse files
authored
Update testing documentation to include withApp(configure:) method (#1086)
1 parent 2b6491e commit 0071b73

File tree

1 file changed

+35
-23
lines changed

1 file changed

+35
-23
lines changed

docs/advanced/testing.md

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -56,35 +56,26 @@ To ensure your tests run in a serialized manner (e.g., when testing with a datab
5656

5757
### Testable Application
5858

59-
Define a private method function `withApp` to streamline and standardize the setup and teardown for our tests. This method encapsulates the lifecycle management of the `Application` instance, ensuring that the application is properly initialized, configured, and shut down for each test.
59+
To provide a streamlined and standardized setup and teardown of tests, `VaporTesting` offers the `withApp` helper function. This method encapsulates the lifecycle management of the `Application` instance, ensuring that the application is properly initialized, configured, and shut down for each test.
6060

61-
In particular it is important to release the threads the application requests at startup. If you do not call `asyncShutdown()` on the app after each unit test, you may find your test suite crash with a precondition failure when allocating threads for a new instance of `Application`.
61+
Pass your application's `configure(_:)` method to the `withApp` helper function to make sure all your routes get correctly registered:
6262

6363
```swift
64-
private func withApp(_ test: (Application) async throws -> ()) async throws {
65-
let app = try await Application.make(.testing)
66-
do {
67-
try await configure(app)
68-
try await test(app)
69-
}
70-
catch {
71-
try await app.asyncShutdown()
72-
throw error
64+
@Test func someTest() async throws {
65+
try await withApp(configure: configure) { app in
66+
// your actual test
7367
}
74-
try await app.asyncShutdown()
7568
}
7669
```
7770

78-
Pass the `Application` to your package's `configure(_:)` method to apply your configuration. Then you test the application calling the `test()` method. Any test-only configurations can also be applied.
79-
8071
#### Send Request
8172

8273
To send a test request to your application, use the `withApp` private method and inside use the `app.testing().test()` method:
8374

8475
```swift
8576
@Test("Test Hello World Route")
8677
func helloWorld() async throws {
87-
try await withApp { app in
78+
try await withApp(configure: configure) { app in
8879
try await app.testing().test(.GET, "hello") { res async in
8980
#expect(res.status == .ok)
9081
#expect(res.body.string == "Hello, world!")
@@ -131,22 +122,28 @@ app.testing(method: .running(port: 8123)).test(...)
131122

132123
#### Database Integration Tests
133124

134-
Configure the database specifically for testing to ensure that your live database is never used during tests.
125+
Configure the database specifically for testing to ensure that your live database is never used during tests. For example, when you are using SQLite, you could configure your database in the `configure(_:)` function as follows:
135126

136127
```swift
137-
app.databases.use(.sqlite(.memory), as: .sqlite)
138-
```
128+
public func configure(_ app: Application) async throws {
129+
// All other configurations...
139130

140-
Then you can enhance your tests by using `autoMigrate()` and `autoRevert()` to manage the database schema and data lifecycle during testing:
131+
if app.environment == .testing {
132+
app.databases.use(.sqlite(.memory), as: .sqlite)
133+
} else {
134+
app.databases.use(.sqlite(.file("db.sqlite")), as: .sqlite)
135+
}
136+
}
137+
```
141138

142-
By combining these methods, you can ensure that each test starts with a fresh and consistent database state, making your tests more reliable and reducing the likelihood of false positives or negatives caused by lingering data.
139+
!!! warning
140+
Make sure you run your tests against the correct database, so you prevent accidentally overwriting data you do not want to lose.
143141

144-
Here's how the `withApp` function looks with the updated configuration:
142+
Then you can enhance your tests by using `autoMigrate()` and `autoRevert()` to manage the database schema and data lifecycle during testing. To do so, you should create your own helper function `withAppIncludingDB` that includes the database schema and data lifecycles:
145143

146144
```swift
147-
private func withApp(_ test: (Application) async throws -> ()) async throws {
145+
private func withAppIncludingDB(_ test: (Application) async throws -> ()) async throws {
148146
let app = try await Application.make(.testing)
149-
app.databases.use(.sqlite(.memory), as: .sqlite)
150147
do {
151148
try await configure(app)
152149
try await app.autoMigrate()
@@ -162,6 +159,21 @@ private func withApp(_ test: (Application) async throws -> ()) async throws {
162159
}
163160
```
164161

162+
And then use this helper in your tests:
163+
```swift
164+
@Test func myDatabaseIntegrationTest() async throws {
165+
try await withAppIncludingDB { app in
166+
try await app.testing().test(.GET, "hello") { res async in
167+
#expect(res.status == .ok)
168+
#expect(res.body.string == "Hello, world!")
169+
}
170+
}
171+
}
172+
```
173+
174+
By combining these methods, you can ensure that each test starts with a fresh and consistent database state, making your tests more reliable and reducing the likelihood of false positives or negatives caused by lingering data.
175+
176+
165177
## XCTVapor
166178

167179
Vapor includes a module named `XCTVapor` that provides test helpers built on `XCTest`. These testing helpers allow you to send test requests to your Vapor application programmatically or running over an HTTP server.

0 commit comments

Comments
 (0)