Skip to content

Instantly share code, notes, and snippets.

@olithissen
Last active November 12, 2024 08:41
Show Gist options
  • Save olithissen/0855d0c21880f98f0df69e67a60891ea to your computer and use it in GitHub Desktop.
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 ...
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())));
}
}
}
@marcphilipp
Copy link

I think it would be safer to make AssertCollector implement ExtensionContext.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.

public class SoftAssertionsTests {

	@RegisterExtension
	Extension parameterResolver = new ParameterResolver() {
		@Override
		public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
				throws ParameterResolutionException {
			return AssertCollector.class.equals(parameterContext.getParameter().getType());
		}

		@Override
		public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
				throws ParameterResolutionException {
			return extensionContext.getStore(ExtensionContext.Namespace.create(getClass(), parameterContext.getParameter()))
					.getOrComputeIfAbsent(AssertCollector.class, __ -> new AssertCollector(), AssertCollector.class);
		}
	};

	@Test
	void test(AssertCollector collector) {
		collector.add(() -> fail("1"));
		collector.add(() -> fail("2"));
	}
}

@olithissen
Copy link
Author

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.

@sormuras @sbrannen @marcphilipp

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment