Guía de reglas de JUnit 4

1. Información general

En este tutorial, veremos la función de Reglas proporcionada por la biblioteca JUnit 4.

Comenzaremos presentando el modelo de reglas JUnit antes de recorrer las reglas básicas más importantes proporcionadas por la distribución. Además, también veremos cómo escribir y usar nuestra propia regla JUnit personalizada.

Para obtener más información sobre las pruebas con JUnit, consulte nuestra completa serie JUnit.

Tenga en cuenta que si está utilizando JUnit 5, las reglas han sido reemplazadas por el modelo de extensión.

2. Introducción a las reglas de JUnit 4

Las reglas de JUnit 4 proporcionan un mecanismo flexible para mejorar las pruebas ejecutando código alrededor de la ejecución de un caso de prueba . En cierto sentido, es similar a tener @Before y @After anotaciones en nuestra clase de prueba.

Imaginemos que quisiéramos conectarnos a un recurso externo como una base de datos durante la configuración de la prueba y luego cerrar la conexión después de que finalice nuestra prueba. Si queremos usar esa base de datos en múltiples pruebas, terminaríamos duplicando ese código en cada prueba.

Al usar una regla, podemos tener todo aislado en un solo lugar y reutilizar el código fácilmente de múltiples clases de prueba.

3. Uso de las reglas de JUnit 4

Entonces, ¿cómo podemos usar las reglas? Podemos usar las reglas de JUnit 4 siguiendo estos sencillos pasos:

  • Agregue un campo público a nuestra clase de prueba y asegúrese de que el tipo de este campo sea un subtipo de la interfaz org.junit.rules.TestRule
  • Anote el campo con la anotación @Rule

En la siguiente sección, veremos qué dependencias del proyecto necesitamos para comenzar.

4. Dependencias de Maven

Primero, agreguemos las dependencias del proyecto que necesitaremos para nuestros ejemplos. Solo necesitaremos la biblioteca principal de JUnit 4:

 junit junit 4.12  

Como siempre, podemos obtener la última versión de Maven Central.

5. Reglas proporcionadas en la distribución

Por supuesto, JUnit proporciona una serie de reglas predefinidas útiles como parte de la biblioteca . Podemos encontrar todas estas reglas en el paquete org.junit.rules .

En esta sección, veremos algunos ejemplos de cómo usarlos.

5.1. La regla TemporaryFolder

Al realizar pruebas, a menudo necesitamos acceso a un archivo o carpeta temporal. Sin embargo, la gestión de la creación y eliminación de estos archivos puede resultar engorrosa. Usando la regla TemporaryFolder , podemos administrar la creación de archivos y carpetas que deben eliminarse cuando finaliza el método de prueba :

@Rule public TemporaryFolder tmpFolder = new TemporaryFolder(); @Test public void givenTempFolderRule_whenNewFile_thenFileIsCreated() throws IOException { File testFile = tmpFolder.newFile("test-file.txt"); assertTrue("The file should have been created: ", testFile.isFile()); assertEquals("Temp folder and test file should match: ", tmpFolder.getRoot(), testFile.getParentFile()); }

Como podemos ver, primero definimos la regla TemporaryFolder tmpFolder . A continuación, nuestro método de prueba crea un archivo llamado test-file.txt en la carpeta temporal. Luego, verificamos que el archivo se haya creado y exista donde debería. ¡Realmente agradable y simple!

Cuando finaliza la prueba, se deben eliminar la carpeta temporal y el archivo. Sin embargo, esta regla no verifica si la eliminación es exitosa o no.

También hay algunos otros métodos interesantes que vale la pena mencionar en esta clase:

  • newFile()

    Si no proporcionamos ningún nombre de archivo, este método crea un nuevo archivo con nombre aleatorio.

  • newFolder(String... folderNames)

    Para crear carpetas temporales recursivamente profundas, podemos usar este método.

  • newFolder()

    Asimismo, el método newFolder () crea una nueva carpeta con un nombre aleatorio.

Una buena adición que vale la pena mencionar es que a partir de la versión 4.13, la regla TemporaryFolder permite la verificación de los recursos eliminados:

@Rule public TemporaryFolder folder = TemporaryFolder.builder().assureDeletion().build();

Si no se puede eliminar un recurso, la prueba fallará con un AssertionError .

Finalmente, en JUnit 5, podemos lograr la misma funcionalidad usando la extensión Directorio temporal.

5.2. La regla de excepción esperada

Como sugiere el nombre, podemos usar la regla ExpectedException para verificar que algún código arroja una excepción esperada:

@Rule public final ExpectedException thrown = ExpectedException.none(); @Test public void givenIllegalArgument_whenExceptionThrown_MessageAndCauseMatches() { thrown.expect(IllegalArgumentException.class); thrown.expectCause(isA(NullPointerException.class)); thrown.expectMessage("This is illegal"); throw new IllegalArgumentException("This is illegal", new NullPointerException()); }

Como podemos ver en el ejemplo anterior, primero declaramos la regla ExpectedException . Luego, en nuestra prueba, afirmamos que se lanza una IllegalArgumentException .

Usando esta regla, también podemos verificar algunas otras propiedades de la excepción, como el mensaje y la causa.

Para obtener una guía detallada sobre cómo probar excepciones con JUnit, consulte nuestra excelente guía sobre cómo afirmar una excepción.

5.3. La regla TestName

En pocas palabras, la regla TestName proporciona el nombre de la prueba actual dentro de un método de prueba dado:

@Rule public TestName name = new TestName(); @Test public void givenAddition_whenPrintingTestName_thenTestNameIsDisplayed() { LOG.info("Executing: {}", name.getMethodName()); assertEquals("givenAddition_whenPrintingTestName_thenTestNameIsDisplayed", name.getMethodName()); }

En este ejemplo trivial, cuando ejecutamos la prueba unitaria, deberíamos ver el nombre de la prueba en la salida:

INFO c.baeldung.rules.JUnitRulesUnitTest - Executing: givenAddition_whenPrintingTestName_thenTestNameIsDisplayed

5.4. La regla del tiempo de espera

En el siguiente ejemplo, veremos la regla de tiempo de espera . Esta regla ofrece una alternativa útil al uso del parámetro de tiempo de espera en una anotación de prueba individual .

Ahora, veamos cómo usar esta regla para establecer un tiempo de espera global en todos los métodos de prueba en nuestra clase de prueba:

@Rule public Timeout globalTimeout = Timeout.seconds(10); @Test public void givenLongRunningTest_whenTimout_thenTestFails() throws InterruptedException { TimeUnit.SECONDS.sleep(20); }

En el ejemplo trivial anterior, primero definimos un tiempo de espera global para todos los métodos de prueba de 10 segundos . Luego definimos deliberadamente una prueba que tomará más de 10 segundos.

Cuando ejecutamos esta prueba, deberíamos ver una falla en la prueba:

org.junit.runners.model.TestTimedOutException: test timed out after 10 seconds ...

5.5. La regla ErrorCollector

A continuación, vamos a echar un vistazo a la regla ErrorCollector . Esta regla permite que la ejecución de una prueba continúe después de que se encuentre el primer problema .

Veamos cómo podemos usar esta regla para recopilar todos los errores e informarlos todos a la vez cuando finaliza la prueba:

@Rule public final ErrorCollector errorCollector = new ErrorCollector(); @Test public void givenMultipleErrors_whenTestRuns_thenCollectorReportsErrors() { errorCollector.addError(new Throwable("First thing went wrong!")); errorCollector.addError(new Throwable("Another thing went wrong!")); errorCollector.checkThat("Hello World", not(containsString("ERROR!"))); }

En el ejemplo anterior, agregamos dos errores al recopilador. Cuando ejecutamos la prueba, la ejecución continúa, pero la prueba fallará al final.

En la salida, veremos ambos errores reportados:

java.lang.Throwable: First thing went wrong! ... java.lang.Throwable: Another thing went wrong!

5.6. La regla del verificador

The Verifier rule is an abstract base class that we can use when we wish to verify some additional behavior from our tests. In fact, the ErrorCollector rule we saw in the last section extends this class.

Let's now take a look at a trivial example of defining our own verifier:

private List messageLog = new ArrayList(); @Rule public Verifier verifier = new Verifier() { @Override public void verify() { assertFalse("Message Log is not Empty!", messageLog.isEmpty()); } }; 

Here, we define a new Verifier and override the verify() method to add some extra verification logic. In this straightforward example, we simply check to see that the message log in our example isn't empty.

Now, when we run the unit test and add a message, we should see that our verifier has been applied:

@Test public void givenNewMessage_whenVerified_thenMessageLogNotEmpty() { // ... messageLog.add("There is a new message!"); }

5.7. The DisableOnDebug Rule

Sometimes we may want to disable a rule when we're debugging. For example, it’s often desirable to disable a Timeout rule when debugging to avoid our test timing out and failing before we've had time to debug it properly.

The DisableOnDebug Rule does precisely this and allows us to label certain rules to be disabled when debugging:

@Rule public DisableOnDebug disableTimeout = new DisableOnDebug(Timeout.seconds(30));

In the example above we can see that in order to use this rule, we simply pass the rule we want to disable to the constructor.

The main benefit of this rule is that we can disable rules without making any modifications to our test classes during debugging.

5.8. The ExternalResource Rule

Typically, when writing integration tests, we may wish to set up an external resource before a test and tear it down afterward. Thankfully, JUnit provides another handy base class for this.

We can extend the abstract class ExternalResource to set up an external resource before a test, such as a file or a database connection. In fact, the TemporaryFolder rule we saw earlier extends ExternalResource.

Let's take a quick look at how we could extend this class:

@Rule public final ExternalResource externalResource = new ExternalResource() { @Override protected void before() throws Throwable { // code to set up a specific external resource. }; @Override protected void after() { // code to tear down the external resource }; };

In this example, when we define an external resource we simply need to override the before() method and after() method in order to set up and tear down our external resource.

6. Applying Class Rules

Up until now, all the examples we've looked at have applied to single test case methods. However, sometimes we might want to apply a rule at the test class level. We can accomplish this by using the @ClassRule annotation.

This annotation works very similarly to @Rule but wraps a rule around a whole test — the main difference being that the field we use for our class rule must be static:

@ClassRule public static TemporaryFolder globalFolder = new TemporaryFolder();

7. Defining a Custom JUnit Rule

As we've seen, JUnit 4 provides a number of useful rules out of the box. Of course, we can define our own custom rules. To write a custom rule, we need to implement the TestRule interface.

Let's take a look at an example of defining a custom test method name logger rule:

public class TestMethodNameLogger implements TestRule { private static final Logger LOG = LoggerFactory.getLogger(TestMethodNameLogger.class); @Override public Statement apply(Statement base, Description description) { logInfo("Before test", description); try { return new Statement() { @Override public void evaluate() throws Throwable { base.evaluate(); } }; } finally { logInfo("After test", description); } } private void logInfo(String msg, Description description) { LOG.info(msg + description.getMethodName()); } }

As we can see, the TestRule interface contains one method called apply(Statement, Description) that we must override to return an instance of Statement. The statement represents our tests within the JUnit runtime. When we call the evaluate() method, this executes our test.

In this example, we log a before and after message and include from the Description object the method name of the individual test.

8. Using Rule Chains

In this final section, we'll take a look at how we can order several test rules using the RuleChain rule:

@Rule public RuleChain chain = RuleChain.outerRule(new MessageLogger("First rule")) .around(new MessageLogger("Second rule")) .around(new MessageLogger("Third rule"));

En el ejemplo anterior, creamos una cadena de tres reglas que simplemente imprimen el mensaje pasado a cada constructor de MessageLogger .

Cuando ejecutamos nuestra prueba, veremos cómo se aplica la cadena en orden:

Starting: First rule Starting: Second rule Starting: Third rule Finished: Third rule Finished: Second rule Finished: First rule

9. Conclusión

Para resumir, en este tutorial, hemos explorado las reglas de JUnit 4 en detalle.

Primero, comenzamos explicando qué son las reglas y cómo podemos usarlas. A continuación, analizamos en profundidad las reglas que forman parte de la distribución JUnit.

Por último, vimos cómo podemos definir nuestra propia regla personalizada y cómo encadenar reglas.

Como siempre, el código fuente completo del artículo está disponible en GitHub.