Los macros expect(_:_:sourceLocation:) y require(_:_:sourceLocation:) pueden ser usados para validar los resultados y errores esperados.
Validar que el código arroje un error esperado
Para el siguiente análisis, considerar el siguiente código de producción:
enum SomeError: Swift.Error {
case error, errorEsperado, otroError
}
class FailingFeature {
var error: SomeError?
func execute() throws(SomeError) {
if let error {
throw error
}
}
}
Caso 1: Una función que arroja error, aunque no debería
Dada una función que potencialmente puede arrojar un error (e.g. execute() throws), se puede construir una prueba que esté marcada con throws para hacer el llamado del código de producción con try. Luego, si el código de producción arroja un error, la prueba falla.
@Test
func basicThrowingTest() throws {
let sut = FailingFeature()
try sut.execute() // ⚠️ Falla si arroja error
}
Caso 2: Validar un error específico
Para verificar que el código de producción arroje un error específico, se lo puede pasar como primer argumento a expect(throws:_:sourceLocation:performing:) y luego pasarle un closure que invoque el código a probar.
@Test
func captureSpecificError() throws {
let sut = FailingFeature()
sut.error = .error
#expect(throws: SomeError.error) { // ✅ Continúa aunque falle
try sut.execute()
}
try #require(throws: SomeError.error) { // ❌ Falla y para
try sut.execute()
}
}
Tener en cuenta que también se puede usar require(throws:_:sourceLocation:performing:), y que la diferencia radica en que expect deja que la prueba continúe, mientras que require arroja un error y provoca que la prueba pare.
Notar que el primer parámetro, tanto de la firma de #expect como #require es throws error: E lo cual indica que se espera recibir un error específico.
Caso 3: Validar un error de un tipo específico
Para validar que el código bajo prueba arroja un error de un tipo específico, se debe pasar el tipo (e.g. SomeError.self) como el primer argumento de expect(throws:_:sourceLocation:performing:) o require(throws:_:sourceLocation:performing:):
@Test
func captureSpecificErrorType() throws {
let sut = FailingFeature()
sut.error = .error
#expect(throws: SomeError.self) { // ✅ Continúa aunque falle
try sut.execute()
}
try #require(throws: SomeError.self) { // ❌ Falla y para
try sut.execute()
}
}
Notar que el primer parámetro, tanto de la firma de #expect como #require es throws errorType: E.Type, lo cual indica que se espera recibir un TIPO de error específico.
Caso 4: Validar un error de cualquier tipo
Como extensión del caso anterior, si el tipo de error que quiero validar es cualquiera, tendría que usar (any Error).self como el primer argumento de expect(throws:_:sourceLocation:performing:) o require(throws:_:sourceLocation:performing:):
@Test
func captureAnyError() throws {
let sut = FailingFeature()
sut.error = .error
#expect(throws: (any Swift.Error).self) { // ✅ Continúa aunque falle
try sut.execute()
}
try #require(throws: (any Swift.Error).self) { // ❌ Falla y para
try sut.execute()
}
}
Caso 5: Validar que el código no arroje error
Generalmente basta con hacer que la firma de la función de prueba arroje (se modificada con throws) para detectar que el código de producción no arroje nada. Sin embargo, en esta implementación la prueba se detiene cuando ocurre la excepción. Por esta razón, puede ser conveniente capturar el error sin detener el resto de la función de prueba. En este caso, el tipo del error esperado sería Never.
@Test
func captureNoError() {
let sut = FailingFeature()
#expect(throws: Never.self) { // ✅ Espera nunca (Never) recibir error
try sut.execute()
}
// ❌ No tiene sentido usar #require
// try #require(throws: (any Swift.Error).self) {
// try sut.execute()
// }
}
Caso 6: Inspeccionar el error arrojado
Al usar #expect(throws:) o #require(throws:), si el error concuerda con el esperado, se retorna a la función de prueba invocadora para que se pueda llevar a cabo alguna validación adicional.
Si la expectativa falla porque no hubo error, o se recibió un error diferente al esperado, entonces #expect(throws:) retorna nil.
@Test
func processError() throws {
let sut = FailingFeature()
sut.error = .errorEsperado
let error = #expect(throws: SomeError.self) {
try sut.execute()
}
if case .errorEsperado = error {
// Continuar la prueba dado que el error es .errorEsperado
} else if case .otroError = error {
// Darle otro manejo a la prueba cuando ocurre otroError
}
}
Bibliografía
- Documentación sobre Swift Testing, aquí.
- Documentación sobre require (comprobar que el código arroje un error)
- Documentación "Testing for errors in Swift code", aquí.
Top comments (0)