You can create your own AfterEachCallback extension and apply it to the needed test methods. This extension will execute after every test it's applied to. Then, using custom annotations, you can link specific cleanup methods with specific tests. Here's an example of the extension:
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.List;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
import org.junit.jupiter.api.extension.ExtensionContext.Store;
import org.junit.platform.commons.support.AnnotationSupport;
import org.junit.platform.commons.support.HierarchyTraversalMode;
public class CleanupExtension implements AfterEachCallback {
private static final Namespace NAMESPACE = Namespace.create(CleanupExtension.class);
private static boolean namesMatch(Method method, String name) {
return method.getAnnotation(CleanMethod.class).value().equals(name);
}
private static Exception suppressOrReturn(final Exception previouslyThrown,
final Exception newlyThrown) {
if (previouslyThrown == null) {
return newlyThrown;
}
previouslyThrown.addSuppressed(newlyThrown);
return previouslyThrown;
}
@Override
public void afterEach(final ExtensionContext context) throws Exception {
final Method testMethod = context.getRequiredTestMethod();
final Cleanup cleanupAnno = testMethod.getAnnotation(Cleanup.class);
final String cleanupName = cleanupAnno == null ? "" : cleanupAnno.value();
final List<Method> cleanMethods = getAnnotatedMethods(context);
final Object testInstance = context.getRequiredTestInstance();
Exception exception = null;
for (final Method method : cleanMethods) {
if (namesMatch(method, cleanupName)) {
try {
method.invoke(testInstance);
} catch (Exception ex) {
exception = suppressOrReturn(exception, ex);
}
}
}
if (exception != null) {
throw exception;
}
}
@SuppressWarnings("unchecked")
private List<Method> getAnnotatedMethods(final ExtensionContext methodContext) {
// Use parent (Class) context so methods are cached between tests if needed
final Store store = methodContext.getParent().orElseThrow().getStore(NAMESPACE);
return store.getOrComputeIfAbsent(
methodContext.getRequiredTestClass(),
this::findAnnotatedMethods,
List.class
);
}
private List<Method> findAnnotatedMethods(final Class<?> testClass) {
final List<Method> cleanMethods = AnnotationSupport.findAnnotatedMethods(testClass,
CleanMethod.class, HierarchyTraversalMode.TOP_DOWN);
for (final Method method : cleanMethods) {
if (method.getParameterCount() != 0) {
throw new IllegalStateException("Methods annotated with "
+ CleanMethod.class.getName() + " must not have parameters: "
+ method
);
}
}
return cleanMethods;
}
@ExtendWith(CleanupExtension.class)
@Retention(RUNTIME)
@Target(METHOD)
public @interface Cleanup {
String value() default "";
}
@Retention(RUNTIME)
@Target(METHOD)
public @interface CleanMethod {
String value() default "";
}
}
And then your test class could look like:
import org.junit.jupiter.api.Test;
class Tests {
@Test
@CleanupExtension.Cleanup
void testWithExtension() {
System.out.println("#testWithExtension()");
}
@Test
void testWithoutExtension() {
System.out.println("#testWithoutExtension()");
}
@Test
@CleanupExtension.Cleanup("alternate")
void testWithExtension_2() {
System.out.println("#testWithExtension_2()");
}
@CleanupExtension.CleanMethod
void performCleanup() {
System.out.println("#performCleanup()");
}
@CleanupExtension.CleanMethod("alternate")
void performCleanup_2() {
System.out.println("#performCleanup_2()");
}
}
Running Tests I get the following output:
#testWithExtension()
#performCleanup()
#testWithExtension_2()
#performCleanup_2()
#testWithoutExtension()
This extension will be applied to any test method annotated with CleanupExtension.Cleanup or ExtendWith(CleanupExtension.class). The purpose of the former annotation is to combine configuration with an annotation that also applies the extension. Then, after each test method the extension will invoke any methods in the class hierarchy that is annotated with CleanupExtension.CleanMethod. Both Cleanup and CleanMethod have a String attribute. This attribute is the "name" and only CleanMethods that have a matching "name" to the Cleanup test will be executed. This allows you to link specific test methods to specific cleanup methods.
For more information on JUnit Jupiter extensions see §5 of the User Guide. Also, for CleanupExtension.Cleanup I'm using the Meta-Annotation/Composed-Annotation feature described in §3.1.1.
Note this is more complicated than the answer given by @Roman Konoval but it may be more user friendly if you have to do this sort of thing many times. However, if you only need to do this for one or two test classes I recommend Roman's answer.