Home > database >  Use a non-mandatory Exception to cause succeed on a junit test in java
Use a non-mandatory Exception to cause succeed on a junit test in java

Time:08-16

I'm looking for a way to cause a succeed through an custom exception without expecting it all the time in junit4. Is this possible with a testrule or something, without touching every single testcase?

I know these options exist but then the exception is expected and the test fails, if no exception is thrown. I want the test to continue even if no exception is thrown and just use the exception to end the test in some special cases through aspectj.

@Test(TestSuccessException.class)
public void testCase() {
    ...
}
public class TestClass{
    @Rule
    public ExpectedException thrown = ExpectedException.none();

    @Test
    public void someTest() {
    thrown.expect(MyRuntimeException.class);

        ...
    }
}

CodePudding user response:

As far as the junit4 source code looks, there isn't a way to achieve this. The only way I found is by customizing the runner itself.

So something like this:

public class CustomTestRunner extends Runner {
    private Class testClass;

    public CustomTestRunner(Class testClass) {
        super();
        this.testClass = testClass;
    }

    @Override
    public Description getDescription() {
        return Description.EMPTY;
    }

    @Override
    public void run(RunNotifier notifier) {
        // Load all methods with @Test annotation from the given class and fire the execution
        try {
            Object testObject = testClass.getConstructor().newInstance();
            for (Method method : testClass.getMethods()) {
                if (method.isAnnotationPresent(Test.class)) {
                    fire(notifier, testObject, method);
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private void fire(RunNotifier notifier, Object testObject, Method method) throws IllegalAccessException, InvocationTargetException {
        notifier.fireTestStarted(Description
                .createTestDescription(testClass, method.getName()));

        try {
            // Call the test method
            method.invoke(testObject);
        } catch (InvocationTargetException e) {
            // method.invoke wraps the original exception with InvocationTargetException
            // The original exception is accessible via getCause()
            // Check if the type of the original exception is the custom "early exist" exception
            // If it doesn't match, throw the exception again; otherwise, ignore and mark the test as successful
            if (!(e.getCause() instanceof EndTestEarlyException)) {
                throw e;
            }
        }

        notifier.fireTestFinished(Description
                .createTestDescription(testClass, method.getName()));
    }
}

You can use this by annotating the Test class as follows:

@RunWith(CustomTestRunner.class)
class MyIntegrationTest {
...
}

Note: Runner is the most generic Runner possible. You could also attempt overriding a more specific runner if you already use one.

Edit: As you are working with legacy, I intentionally tried not to use newer language features, like generics (Class<?>).

The solution is based on this baeldung article.

Junit5

Last but not least: This is probably not relevant in your particular case but might be interesting for future readers. If you manage to upgrade to Junit5, you could handle this within an extension.

You could implement a custom extension like this:

public class IgnoreEndTestEarlyException implements TestExecutionExceptionHandler {
    @Override
    public void handleTestExecutionException(ExtensionContext context,
      Throwable throwable) throws Throwable {

        if (throwable instanceof EndTestEarlyException ) {
            return;
        }

        throw throwable;
    }
}

And use it like this:

@ExtendWith(IgnoreEndTestEarlyException.class)
public class MyIntegrationTest

I tend to create another annotation (something like @IntegrationTest), put the @ExtendsWith on there, and use the new annotation.

It would be cleaner and easier to add multiple extensions. You can run Junit4 and Junit5 within the same module, but you must replace all annotations within your integration test suit. It might not be worth the effort for multiple thousand tests.

  • Related