void (@NotNull Class<? extends Throwable> exceptionClass, @Nullable String expectedErrorMsgPart, @NotNull ThrowableRunnable<?> runnable) { boolean wasThrown = false; try { runnable.run(); } catch (Throwable e) { Throwable cause = e; while (cause instanceof TestLoggerFactory.TestLoggerAssertionError && cause.getCause() != null) { cause = cause.getCause(); } wasThrown = true; if (!exceptionClass.isInstance(cause)) { throw new AssertionError("Expected instance of: " + exceptionClass + " actual: " + cause.getClass(), cause); } if (expectedErrorMsgPart != null) { assertTrue(cause.getClass()+" message was expected to contain '"+expectedErrorMsgPart+"', but got: '"+cause.getMessage()+"'", cause.getMessage().contains(expectedErrorMsgPart)); } } finally { if (!wasThrown) { fail("'" + exceptionClass.getName() + "' must have been thrown, but the computation completed successfully instead"); } } }