Testing¶
VaporTesting¶
Vapor incluye un módulo llamado VaporTesting
que proporciona métodos auxiliares de test basados en Swift Testing
. Estos métodos de pruebas te permiten enviar solicitudes de prueba a tu aplicación Vapor programáticamente o ejecutándose sobre un servidor HTTP.
Nota
Para nuevos proyectos o equipos que adopten la concurrencia de Swift, Se recomienda usar Swift Testing
por encima de XCTest
.
Primeros Pasos¶
Para usar el módulo VaporTesting
, asegúrate de que ha sido añadido al target de test de tu paquete.
let package = Package(
...
dependencies: [
.package(url: "https://github.com/vapor/vapor.git", from: "4.110.1")
],
targets: [
...
.testTarget(name: "AppTests", dependencies: [
.target(name: "App"),
.product(name: "VaporTesting", package: "vapor"),
])
]
)
Advertencia
Asegúrate de usar el módulo de testing correspondiente, de no hacerlo puede provocar que los fallos de las prueba de Vapor no sean informados correctamente.
Luego, añade ìmport VaporTesting
e ìmport Testing
al principio de tus archivos de prueba. Crea estructuras con el nombre @Suite
para escribir casos de prueba.
@testable import App
import VaporTesting
import Testing
@Suite("App Tests")
struct AppTests {
@Test("Test Stub")
func stub() async throws {
// Prueba aquí.
}
}
Cada función marcada con @Test
se ejecutará automáticamente cuando se pruebe tu aplicación.
Para garantizar que tus pruebas se ejecuten de manera serializada (por ejemplo, al realizar pruebas con una base de datos), incluye la opción .serialized
en la declaración de la suite de pruebas.
@Suite("App Tests with DB", .serialized)
Probando la Aplicación¶
Define una función de método privado withApp
para agilizar y estandarizar la configuración y el cierre de nuestras pruebas. Este método encapsula la gestión del ciclo de vida de la instancia Application
, asegurando que la aplicación está correctamente inicializada, configurada y apagada para cada prueba.
En particular, es importante liberar los subprocesos que solicita la aplicación al iniciarse. Si no llamas a asyncShutdown()
en la aplicación después de cada prueba unitaria, es posible que tu conjunto de pruebas se bloquee con un error de condición previa al asignar subprocesos para una nueva instancia de Application
.
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)
}
catch {
try await app.asyncShutdown()
throw error
}
try await app.asyncShutdown()
}
Pasa Application
al método configure(_:)
de tu paquete para aplicar tu configuración. Luego, prueba la aplicación llamando al método test()
. También se puede aplicar cualquier configuración que sólo sea de prueba.
Enviar Solicitud¶
Para enviar una solicitud de prueba a tu aplicación, usa el método privado withApp
y, dentro, usa el método app.testing().test()
:
@Test("Test Hello World Route")
func helloWorld() async throws {
try await withApp { app in
try await app.testing().test(.GET, "hello") { res async in
#expect(res.status == .ok)
#expect(res.body.string == "Hello, world!")
}
}
}
Los dos primeros parámetros son el método HTTP y la URL a solicitar. El closure final acepta la respuesta HTTP que puedes verificar usando la macro #expect
.
Para solicitudes más complejas, puedes proporcionar un closure beforeRequest
para modificar los encabezados o codificar el contenido. La API de contenido de Vapor está disponible tanto en la solicitud de prueba como en la respuesta.
let newDTO = TodoDTO(id: nil, title: "test")
try await app.testing().test(.POST, "todos", beforeRequest: { req in
try req.content.encode(newDTO)
}, afterResponse: { res async throws in
#expect(res.status == .ok)
let models = try await Todo.query(on: app.db).all()
#expect(models.map({ $0.toDTO().title }) == [newDTO.title])
})
Método de Prueba¶
La API de pruebas de Vapor admite el envío de solicitudes de prueba de manera programática y a través de un servidor HTTP activo. Puedes especificar qué método deseas utilizar a través del método testing
.
// Utiliza pruebas programáticas.
app.testing(method: .inMemory).test(...)
// Ejecuta pruebas a través de un servidor HTTP activo.
app.testing(method: .running).test(...)
La opción inMemory
se utiliza de manera predeterminada.
La opción running
admite pasar un puerto específico a usar. De manera predeterminada, se utiliza 8080
.
app.testing(method: .running(port: 8123)).test(...)
Pruebas de Integración de Bases de Datos¶
Configura la base de datos específicamente para realizar pruebas para asegurarse de que tu base de datos activa nunca se utiliza durante las pruebas.
app.databases.use(.sqlite(.memory), as: .sqlite)
Luego, puedes mejorar tus pruebas utilizando autoMigrate()
y autoRevert()
para gestionar el esquema de la base de datos y el ciclo de vida de los datos durante las pruebas:
Al combinar estos métodos, puedes asegurarte de que cada prueba comienza con un estado de base de datos nuevo y consistente, lo que hace que tus pruebas sean más confiables y reduce la probabilidad de falsos positivos o negativos causados por datos persistentes.
Así es como se ve la función withApp
con la configuración actualizada:
private func withApp(_ 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()
try await test(app)
try await app.autoRevert()
}
catch {
try? await app.autoRevert()
try await app.asyncShutdown()
throw error
}
try await app.asyncShutdown()
}
XCTVapor¶
Vapor incluye un módulo llamado XCTVapor
que proporciona ayudas de prueba basadas en XCTest
. Estas ayudas te permiten enviar solicitudes de prueba a tu aplicación Vapor de manera programática o ejecutándose a través de un servidor HTTP.
Primeros Pasos¶
Para utilizar el módulo XCTVapor
, asegúrate de tenerlo agregado al target de prueba de tu paquete.
let package = Package(
...
dependencies: [
.package(url: "https://github.com/vapor/vapor.git", from: "4.0.0")
],
targets: [
...
.testTarget(name: "AppTests", dependencies: [
.target(name: "App"),
.product(name: "XCTVapor", package: "vapor"),
])
]
)
Luego, agrega import XCTVapor
en la parte superior de tus archivos de prueba. Crea clases que extiendan de XCTestCase
para escribir casos de prueba.
import XCTVapor
final class MyTests: XCTestCase {
func testStub() throws {
// Prueba aquí.
}
}
Cada función que comience con test
se ejecutará automáticamente cuando se pruebe tu aplicación.
Probando la Aplicación¶
Inicializa una instancia de Application
utilizando el entorno .testing
. Debes llamar a app.shutdown()
antes de que esta aplicación se desinicialice.
El cierre (shutdown) es necesario para ayudar a liberar los recursos que ha reclamado la aplicación. En particular, es importante liberar los subprocesos que la aplicación solicita al inicio. Si no llamas a shutdown()
en la aplicación después de cada prueba unitaria, es posible que el conjunto de pruebas falle con una condición previa fallida al asignar subprocesos para una nueva instancia de Application
.
let app = Application(.testing)
defer { app.shutdown() }
try configure(app)
Pasa Application
al método configure(_:)
de tu paquete para aplicar su configuración. Cualquier configuración de "solo prueba" se puede aplicar después.
Enviar una Petición¶
Para enviar una solicitud de prueba a tu aplicación, utiliza el método test
.
try app.test(.GET, "hello") { res in
XCTAssertEqual(res.status, .ok)
XCTAssertEqual(res.body.string, "Hello, world!")
}
Los primeros dos parámetros son el método HTTP y la URL a solicitar. El closure final acepta la respuesta HTTP que puedes verificar utilizando los métodos de tipo XCTAssert
.
Para solicitudes más complejas, puedes proporcionar un closure llamado beforeRequest
para modificar encabezados o codificar contenido. La API de Content de Vapor está disponible tanto en la solicitud de prueba como en la respuesta.
try app.test(.POST, "todos", beforeRequest: { req in
try req.content.encode(["title": "Test"])
}, afterResponse: { res in
XCTAssertEqual(res.status, .created)
let todo = try res.content.decode(Todo.self)
XCTAssertEqual(todo.title, "Test")
})
Probando un Método¶
La API de prueba de Vapor admite el envío de solicitudes de prueba de forma programática y a través de un servidor HTTP en vivo. Puedes especificar qué método te gustaría probar utilizando el método testable
.
// Utilizar pruebas programáticas.
app.testable(method: .inMemory).test(...)
// Ejecutar pruebas a través de un servidor HTTP en vivo.
app.testable(method: .running).test(...)
La opción inMemory
se utiliza de forma predeterminada.
La opción running
admite pasar un puerto específico. Por defecto se utiliza el puerto 8080
.
.running(port: 8123)