Last active
November 12, 2024 08:41
-
-
Save olithissen/0855d0c21880f98f0df69e67a60891ea to your computer and use it in GitHub Desktop.
A small Java util class to don't have JUnit 5 fail on each assert. Handy if you have multiple asserts in one test and don't want to fix each failure one by one ...
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package net.tonick.demo | |
import org.junit.jupiter.api.function.Executable; | |
import java.util.ArrayList; | |
import java.util.List; | |
import static org.junit.jupiter.api.Assertions.fail; | |
/** | |
* AssertCollector is a utility for collecting and executing assertions in JUnit tests. | |
* It allows for multiple assertions to be executed and collected for later inspection without | |
* repeating your run, fix, run, fix cycle over and over again. | |
* | |
* <p>Example usage</p> | |
* | |
* <pre>{@code | |
* AssertCollector defaultCollector = new AssertCollector("Demo"); | |
* defaultCollector.add( | |
* () -> assertEquals(1, 1, "Strangely 1 is not equal to 1")), | |
* () -> assertTrue(false, "Wait, false is not true?")) | |
* ); | |
* | |
* // ... | |
* | |
* defaultCollector.add(() -> fail("Fail anyway")); | |
* | |
* // ... | |
* | |
* if (defaultCollector.hasTestFailures()) { | |
* fail(defaultCollector.getName() + " has failures: " + String.join("\n", defaultCollector.getMessages())); | |
* } | |
* | |
* | |
* }</pre> | |
* | |
* Alternatively the test code can be wrapped in a try-with-resources statement to automatically fail on `close()` | |
* if there are messages present. | |
* <pre>{@code | |
* try (AssertCollector defaultCollector = new AssertCollector("Demo")) { | |
* // ... | |
* } | |
* | |
* }</pre> | |
*/ | |
public class AssertCollector implements AutoCloseable { | |
private final String name; | |
private List<String> messages = new ArrayList<>(); | |
/** | |
* Create AssertCollector with default name | |
*/ | |
public AssertCollector() { | |
this("Default"); | |
} | |
/** | |
* Create AssertCollector with a specified name | |
* | |
* @param name The AsserCollector's name | |
*/ | |
public AssertCollector(String name) { | |
this.name = name; | |
} | |
/** | |
* Returns the name of the AssertCollector ... duh... | |
* | |
* @return The AsserCollector's name | |
*/ | |
public String getName() { | |
return name; | |
} | |
/** | |
* Returns the list of messages collected during assertion execution. | |
* | |
* @return the list of messages collected during execution | |
*/ | |
public List<String> getMessages() { | |
return messages; | |
} | |
/** | |
* Checks if there are any of the asserts failed | |
* | |
* @return `true` if there are test failures, `false` otherwise | |
*/ | |
public boolean hasTestFailures() { | |
return !messages.isEmpty(); | |
} | |
/** | |
* Adds and executes the given array of `Executable` assertions. | |
* Any failure messages are collected for later inspection. | |
* | |
* @param executables the array of `Executable` assertions to be executed | |
*/ | |
public void add(Executable... executables) { | |
for (var executable : executables) { | |
try { | |
executable.execute(); | |
} catch (Throwable t) { | |
messages.add(t.getMessage()); | |
} | |
} | |
} | |
@Override | |
public void close() throws Exception { | |
if (hasTestFailures()) { | |
fail("'%s' has failures: %s".formatted(getName(), String.join(System.lineSeparator(), getMessages()))); | |
} | |
} | |
} |
As Gists don't support emoji reactions, I'm giving all this a collective 👍
I didn't expect this kind of feedback from people as deeply involved in JUnit. I'll have a look into all the suggestions.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I think it would be safer to make
AssertCollector
implementExtensionContext.Store.CloseableResource
and inject it as a parameter from an extension. That way, one cannot forget to "close" it. The extension can be implemented as a separate class, of course.