From 19f45cdf2d406dd391f0ee1e58be8e147c26a345 Mon Sep 17 00:00:00 2001 From: Maarten Engels Date: Wed, 23 Jul 2025 15:10:38 +0000 Subject: [PATCH 1/7] Update testing documentation to include withApp(configure:) method --- docs/advanced/testing.md | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/docs/advanced/testing.md b/docs/advanced/testing.md index 713d3772f..3b00ebf28 100644 --- a/docs/advanced/testing.md +++ b/docs/advanced/testing.md @@ -56,27 +56,18 @@ To ensure your tests run in a serialized manner (e.g., when testing with a datab ### Testable Application -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. +To provide a streamlined and standardized setup and teardown of tests, `Vapor-Testing` provides 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. -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`. +Pass your applications `configure(_:)` method with the `withApp` helper function to make sure all your routes get correctly registered: ```swift -private func withApp(_ test: (Application) async throws -> ()) async throws { - let app = try await Application.make(.testing) - do { - try await configure(app) - try await test(app) +@Test func someTest() async throws { + try await withApp(configure: configure) { app in + // your actual test } - catch { - try await app.asyncShutdown() - throw error - } - try await app.asyncShutdown() } ``` -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. - #### Send Request To send a test request to your application, use the `withApp` private method and inside use the `app.testing().test()` method: @@ -84,7 +75,7 @@ To send a test request to your application, use the `withApp` private method and ```swift @Test("Test Hello World Route") func helloWorld() async throws { - try await withApp { app in + try await withApp(configure: configure) { app in try await app.testing().test(.GET, "hello") { res async in #expect(res.status == .ok) #expect(res.body.string == "Hello, world!") @@ -141,10 +132,10 @@ Then you can enhance your tests by using `autoMigrate()` and `autoRevert()` to m 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. -Here's how the `withApp` function looks with the updated configuration: +You should create your own helper function `withAppIncludingDB` that includes the database schema and data lifecycles: ```swift -private func withApp(_ test: (Application) async throws -> ()) async throws { +private func withAppIncludingDB(_ test: (Application) async throws -> ()) async throws { let app = try await Application.make(.testing) app.databases.use(.sqlite(.memory), as: .sqlite) do { @@ -162,6 +153,18 @@ private func withApp(_ test: (Application) async throws -> ()) async throws { } ``` +And then use this helper in your tests: +```swift +@Test func myDatabaseIntegrationTest() async throws { + try await withAppIncludingDB { app in + try await app.testing().test(.GET, "hello") { res async in + #expect(res.status == .ok) + #expect(res.body.string == "Hello, world!") + } + } +} +``` + ## XCTVapor 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. From 8ae41489b0a9f027fbe692b1b5facbeca08e74b7 Mon Sep 17 00:00:00 2001 From: Maarten Engels Date: Sat, 26 Jul 2025 19:07:57 +0200 Subject: [PATCH 2/7] Update docs/advanced/testing.md Co-authored-by: Francesco Paolo Severino <96546612+fpseverino@users.noreply.github.com> --- docs/advanced/testing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/advanced/testing.md b/docs/advanced/testing.md index 3b00ebf28..59c4140ab 100644 --- a/docs/advanced/testing.md +++ b/docs/advanced/testing.md @@ -56,7 +56,7 @@ To ensure your tests run in a serialized manner (e.g., when testing with a datab ### Testable Application -To provide a streamlined and standardized setup and teardown of tests, `Vapor-Testing` provides 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. +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. Pass your applications `configure(_:)` method with the `withApp` helper function to make sure all your routes get correctly registered: From b418bf297567133f6314628ff510ff499d410892 Mon Sep 17 00:00:00 2001 From: Maarten Engels Date: Sat, 26 Jul 2025 19:08:22 +0200 Subject: [PATCH 3/7] Update docs/advanced/testing.md Co-authored-by: Francesco Paolo Severino <96546612+fpseverino@users.noreply.github.com> --- docs/advanced/testing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/advanced/testing.md b/docs/advanced/testing.md index 59c4140ab..22d2d5082 100644 --- a/docs/advanced/testing.md +++ b/docs/advanced/testing.md @@ -58,7 +58,7 @@ To ensure your tests run in a serialized manner (e.g., when testing with a datab 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. -Pass your applications `configure(_:)` method with the `withApp` helper function to make sure all your routes get correctly registered: +Pass your application's `configure(_:)` method to the `withApp` helper function to make sure all your routes get correctly registered: ```swift @Test func someTest() async throws { From dbb5267516f9a6386bf4759df4b16abd7a709991 Mon Sep 17 00:00:00 2001 From: Maarten Engels Date: Wed, 30 Jul 2025 15:22:48 +0000 Subject: [PATCH 4/7] Improved based on feedback --- docs/advanced/testing.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docs/advanced/testing.md b/docs/advanced/testing.md index 3b00ebf28..0edfde627 100644 --- a/docs/advanced/testing.md +++ b/docs/advanced/testing.md @@ -124,10 +124,6 @@ app.testing(method: .running(port: 8123)).test(...) Configure the database specifically for testing to ensure that your live database is never used during tests. -```swift -app.databases.use(.sqlite(.memory), as: .sqlite) -``` - Then you can enhance your tests by using `autoMigrate()` and `autoRevert()` to manage the database schema and data lifecycle during testing: 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. @@ -137,7 +133,6 @@ You should create your own helper function `withAppIncludingDB` that includes th ```swift private func withAppIncludingDB(_ test: (Application) async throws -> ()) async throws { let app = try await Application.make(.testing) - app.databases.use(.sqlite(.memory), as: .sqlite) do { try await configure(app) try await app.autoMigrate() From 606ed26019882b23ff6a5de90e660b0b15840d53 Mon Sep 17 00:00:00 2001 From: Maarten Engels Date: Thu, 7 Aug 2025 09:11:54 +0000 Subject: [PATCH 5/7] Re-added hint for SQLite and make sure we tell people to check the database they want the tests to run on --- docs/advanced/testing.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/advanced/testing.md b/docs/advanced/testing.md index 1a7d8cb84..a067537c1 100644 --- a/docs/advanced/testing.md +++ b/docs/advanced/testing.md @@ -135,6 +135,9 @@ private func withAppIncludingDB(_ test: (Application) async throws -> ()) async let app = try await Application.make(.testing) do { try await configure(app) + // make sure you are not connecting to a production database + // i.e. for SQLite, you can use something like: + // app.databases.use(.sqlite(.memory), as: .sqlite) try await app.autoMigrate() try await test(app) try await app.autoRevert() @@ -148,6 +151,10 @@ private func withAppIncludingDB(_ test: (Application) async throws -> ()) async } ``` +!!! warning + Make sure you run your tests against the correct database, so you prevent accidentally overwriting data you do not want to lose. + + And then use this helper in your tests: ```swift @Test func myDatabaseIntegrationTest() async throws { From 4eb32e6c64cde56b194c4bd14ec6c42be4a924e8 Mon Sep 17 00:00:00 2001 From: Maarten Engels Date: Thu, 7 Aug 2025 10:58:50 +0000 Subject: [PATCH 6/7] Structure now describes both choosing right database and the migrate/revert actions. --- docs/advanced/testing.md | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/docs/advanced/testing.md b/docs/advanced/testing.md index a067537c1..90da65b00 100644 --- a/docs/advanced/testing.md +++ b/docs/advanced/testing.md @@ -122,22 +122,30 @@ app.testing(method: .running(port: 8123)).test(...) #### Database Integration Tests -Configure the database specifically for testing to ensure that your live database is never used during tests. +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: -Then you can enhance your tests by using `autoMigrate()` and `autoRevert()` to manage the database schema and data lifecycle during testing: +```swift +public func configure(_ app: Application) async throws { + // All other configurations... -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. + if app.environment == .testing { + app.databases.use(.sqlite(.memory), as: .sqlite) + } else { + app.databases.use(.sqlite(.file("db.sqlite")), as: .sqlite) + } +} +``` + +!!! warning + Make sure you run your tests against the correct database, so you prevent accidentally overwriting data you do not want to lose. -You should create your own helper function `withAppIncludingDB` that includes the database schema and data lifecycles: +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: ```swift private func withAppIncludingDB(_ test: (Application) async throws -> ()) async throws { let app = try await Application.make(.testing) do { try await configure(app) - // make sure you are not connecting to a production database - // i.e. for SQLite, you can use something like: - // app.databases.use(.sqlite(.memory), as: .sqlite) try await app.autoMigrate() try await test(app) try await app.autoRevert() @@ -151,10 +159,6 @@ private func withAppIncludingDB(_ test: (Application) async throws -> ()) async } ``` -!!! warning - Make sure you run your tests against the correct database, so you prevent accidentally overwriting data you do not want to lose. - - And then use this helper in your tests: ```swift @Test func myDatabaseIntegrationTest() async throws { @@ -167,6 +171,9 @@ And then use this helper in your tests: } ``` +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. + + ## XCTVapor 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. From f324dd25b32789b0f85a09bb417bcd1884c36392 Mon Sep 17 00:00:00 2001 From: Maarten Engels Date: Thu, 7 Aug 2025 11:01:55 +0000 Subject: [PATCH 7/7] fix typo in configure --- docs/advanced/testing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/advanced/testing.md b/docs/advanced/testing.md index 90da65b00..c0a813d1e 100644 --- a/docs/advanced/testing.md +++ b/docs/advanced/testing.md @@ -122,7 +122,7 @@ app.testing(method: .running(port: 8123)).test(...) #### Database Integration Tests -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: +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: ```swift public func configure(_ app: Application) async throws {