exception testing groovy spock

exception - Spock: probando excepciones con tablas de datos



testing groovy (6)

Aquí está la solución que se me ocurrió. Básicamente es la Variante 3, pero usa un bloque de try/catch para evitar el uso de las condiciones de excepción de Spock (ya que esas deben ser de nivel superior).

def "validate user - data table 3 - working"() { expect: try { validateUser(user) assert !expectException } catch (UserException ex) { assert expectException assert ex.message == expectedMessage } where: user || expectException | expectedMessage new User(userName: ''tester'') || false | null new User(userName: null) || true | ''no userName'' null || true | ''no user'' }

Algunas advertencias:

  1. Necesita varios bloques catch para probar diferentes excepciones.
  2. Debe usar condiciones explícitas (afirmaciones assert ) dentro de bloques try / catch.
  3. No puedes separar tus estímulos y tus respuestas en bloques de when-then .

¿Cómo se pueden probar las excepciones de una manera agradable (por ejemplo, tablas de datos) con Spock?

Ejemplo: Tener un método validateUser que pueda lanzar excepciones con diferentes mensajes o sin excepción si el usuario es válido.

La clase de especificación en sí misma:

class User { String userName } class SomeSpec extends spock.lang.Specification { ...tests go here... private validateUser(User user) { if (!user) throw new Exception (''no user'') if (!user.userName) throw new Exception (''no userName'') } }

Variante 1

Este funciona, pero la verdadera intención está atestada de todas las etiquetas when / then y las llamadas repetidas de validateUser(user) .

def ''validate user - the long way - working but not nice''() { when: def user = new User(userName: ''tester'') validateUser(user) then: noExceptionThrown() when: user = new User(userName: null) validateUser(user) then: def ex = thrown(Exception) ex.message == ''no userName'' when: user = null validateUser(user) then: ex = thrown(Exception) ex.message == ''no user'' }

Variante 2

Éste no funciona debido a este error planteado por Spock en el momento de la compilación:

Las condiciones de excepción solo se permiten en bloques ''entonces''

def ''validate user - data table 1 - not working''() { when: validateUser(user) then: check() where: user || check new User(userName: ''tester'') || { noExceptionThrown() } new User(userName: null) || { Exception ex = thrown(); ex.message == ''no userName'' } null || { Exception ex = thrown(); ex.message == ''no user'' } }

Variante 3

Éste no funciona debido a este error planteado por Spock en el momento de la compilación:

Las condiciones de excepción solo se permiten como declaraciones de nivel superior

def ''validate user - data table 2 - not working''() { when: validateUser(user) then: if (expectedException) { def ex = thrown(expectedException) ex.message == expectedMessage } else { noExceptionThrown() } where: user || expectedException | expectedMessage new User(userName: ''tester'') || null | null new User(userName: null) || Exception | ''no userName'' null || Exception | ''no user'' }


Aquí hay un ejemplo de cómo lo @Unroll usando @Unroll y when: then: y where: bloques. Se ejecuta utilizando las 3 pruebas con los datos de la tabla de datos:

import spock.lang.Specification import spock.lang.Unroll import java.util.regex.Pattern class MyVowelString { private static final Pattern HAS_VOWELS = Pattern.compile(''[aeiouAEIOU]'') final String string MyVowelString(String string) { assert string != null && HAS_VOWELS.matcher(string).find() this.string = string } } class PositiveNumberTest extends Specification { @Unroll def "invalid constructors with argument #number"() { when: new MyVowelString(string) then: thrown(AssertionError) where: string | _ '''' | _ null | _ ''pppp'' | _ } }


Así es como lo hago, modifico la cláusula when: para lanzar siempre una excepción de Success , de esa manera no necesita pruebas separadas o lógica para decir si se llama notThrown o no notThrown , simplemente siempre se thrown llamada con la tabla de datos que indica si esperar el Success o no.

Podría cambiar el nombre de Success para que sea None o NoException o lo que prefiera.

class User { String userName } class SomeSpec extends spock.lang.Specification { class Success extends Exception {} def ''validate user - data table 2 - working''() { when: validateUser(user) throw new Success () then: def ex = thrown(expectedException) ex.message == expectedMessage where: user || expectedException | expectedMessage new User(userName: ''tester'') || Success | null new User(userName: null) || Exception | ''no userName'' null || Exception | ''no user'' } private validateUser(User user) { if (!user) throw new Exception (''no user'') if (!user.userName) throw new Exception (''no userName'') } }

Una cosa adicional que cambiaría, sería usar una subclase para las excepciones de falla también para evitar que se Success accidentalmente el Success cuando realmente esperaba una falla. No afecta su ejemplo porque tiene una verificación adicional para el mensaje, pero otras pruebas pueden simplemente probar el tipo de excepción.

class Failure extends Exception {}

y use esa o alguna otra excepción "real" en lugar de la Exception vanilla


La solución recomendada es tener dos métodos: uno que pruebe los casos buenos y otro que pruebe los casos malos. Entonces ambos métodos pueden hacer uso de tablas de datos.

Ejemplo:

class SomeSpec extends Specification { class User { String userName } def ''validate valid user''() { when: validateUser(user) then: noExceptionThrown() where: user << [ new User(userName: ''tester''), new User(userName: ''joe'')] } def ''validate invalid user''() { when: validateUser(user) then: def error = thrown(expectedException) error.message == expectedMessage where: user || expectedException | expectedMessage new User(userName: null) || Exception | ''no userName'' new User(userName: '''') || Exception | ''no userName'' null || Exception | ''no user'' } private validateUser(User user) { if (!user) throw new Exception(''no user'') if (!user.userName) throw new Exception(''no userName'') } }


Puede ajustar su llamada de método con un método que devuelva el mensaje o la clase de excepción, o un mapa de ambos ...

def ''validate user - data table 2 - not working''() { expect: expectedMessage == getExceptionMessage(&validateUser,user) where: user || expectedMessage new User(userName: ''tester'') || null new User(userName: null) || ''no userName'' null || ''no user'' } String getExceptionMessage(Closure c, Object... args){ try{ return c.call(args) //or return null here if you want to check only for exceptions }catch(Exception e){ return e.message } }


Usando el ejemplo de @AmanuelNega, intenté esto en la consola web de spock y guardé el código en http://meetspock.appspot.com/script/5713144022302720

import spock.lang.Specification class MathDemo { static determineAverage(...values) throws IllegalArgumentException { for (item in values) { if (! (item instanceof Number)) { throw new IllegalArgumentException() } } if (!values) { return 0 } return values.sum() / values.size() } } class AvgSpec extends Specification { @Unroll def "average of #values gives #result"(values, result){ expect: MathDemo.determineAverage(*values) == result where: values || result [1,2,3] || 2 [2, 7, 4, 4] || 4.25 [] || 0 } @Unroll def "determineAverage called with #values throws #exception"(values, exception){ setup: def e = getException(MathDemo.&determineAverage, *values) expect: exception == e?.class where: values || exception [''kitten'', 1]|| java.lang.IllegalArgumentException [99, true] || java.lang.IllegalArgumentException [1,2,3] || null } Exception getException(closure, ...args){ try{ closure.call(args) return null } catch(any) { return any } } } ​