/* * Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com * The software in this package is published under the terms of the CPAL v1.0 * license, a copy of which has been included with this distribution in the * LICENSE.txt file. */ package org.mule.functional.junit4.rules; import static com.google.common.base.Throwables.getStackTraceAsString; import static java.lang.String.format; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.instanceOf; import static org.junit.Assert.fail; import static org.mule.tck.junit4.matcher.ErrorTypeMatcher.errorType; import org.mule.runtime.api.message.Error; import org.mule.runtime.core.api.Event; import org.mule.runtime.core.exception.MessagingException; import org.mule.runtime.extension.api.error.ErrorTypeDefinition; import org.mule.tck.junit4.matcher.ErrorTypeMatcher; import com.google.common.base.Joiner; import java.util.ArrayList; import java.util.List; import org.hamcrest.Matcher; import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runners.model.Statement; /** * JUnit rule for Mule errors * * @since 4.0 */ public class ExpectedError implements TestRule { private Matcher<String> messageMatcher; private ErrorTypeMatcher errorTypeMatcher; private Matcher<? extends Throwable> causeMatcher; private List<Matcher> matchers; /** * @return a Rule that expects no error to be thrown (identical to behavior without this Rule) */ public static ExpectedError none() { return new ExpectedError(); } private ExpectedError() { messageMatcher = null; errorTypeMatcher = null; causeMatcher = null; matchers = new ArrayList<>(); } /** * {@inheritDoc} */ @Override public Statement apply(Statement statement, Description description) { return new ExpectedErrorStatement(statement); } /** * Helper method to configure all the matchers that can be used with this rule at the same time. */ public void expectError(String namespace, String errorTypeDefinition, Class<?> cause, String message) { expectErrorType(namespace, errorTypeDefinition) .expectCause(instanceOf(cause)) .expectMessage(containsString(message)); } /** * Helper method to configure all the matchers that can be used with this rule at the same time. */ public void expectError(String namespace, ErrorTypeDefinition errorTypeDefinition, Class<?> cause, String message) { expectError(namespace, errorTypeDefinition.getType(), cause, message); } public ExpectedError expectMessage(Matcher<String> matcher) { this.messageMatcher = matcher; return this; } public ExpectedError expectErrorType(String namespace, String errorType) { this.errorTypeMatcher = errorType(namespace, errorType); return this; } public ExpectedError expectCause(Matcher<? extends Throwable> expectedCause) { this.causeMatcher = expectedCause; return this; } private boolean expectsThrowable() { return errorTypeMatcher != null || causeMatcher != null || messageMatcher != null; } private void failDueToMissingException() { fail(format("No exception was thrown during the test \n %s", this.toString())); } private void failWithNonMatchingException(Exception e) { fail(format("An exception was caught but it didn't met the following conditions:\n %s \n Caught exception was:\n %s %s", Joiner.on("\n").join(matchers).toString(), e, getStackTraceAsString(e))); } private void failDueToUnexpectedException(Throwable e) { fail(format("An exception was caught but it wasn't expected for the test \n Caught exception was:\n %s %s", e, getStackTraceAsString(e))); } private void failDueToExceptionWithoutError(Throwable e, Event event) { fail(format("An exception was caught but it didn't contain information about the error \n Event: \n %s \n Caught exception was:\n %s %s", event, e, getStackTraceAsString(e))); } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("MessagingException matcher with:\n"); if (errorTypeMatcher != null) { builder.append(format("* An error: %s\n", errorTypeMatcher)); } if (causeMatcher != null) { builder.append(format("* A cause: %s\n", causeMatcher)); } if (messageMatcher != null) { builder.append(format("* A message: %s\n", messageMatcher)); } return builder.toString(); } private class ExpectedErrorStatement extends Statement { private final Statement statement; public ExpectedErrorStatement(Statement base) { statement = base; } @Override public void evaluate() throws Throwable { try { statement.evaluate(); } catch (MessagingException exception) { if (!expectsThrowable()) { failDueToUnexpectedException(exception); } Event event = exception.getEvent(); if (!event.getError().isPresent()) { failDueToExceptionWithoutError(exception, event); } Error error = event.getError().get(); if (errorTypeMatcher != null && !errorTypeMatcher.matches(error.getErrorType())) { matchers.add(errorTypeMatcher); } if (causeMatcher != null && !causeMatcher.matches(error.getCause())) { matchers.add(causeMatcher); } if (messageMatcher != null && !messageMatcher.matches(error.getDescription())) { matchers.add(messageMatcher); } if (!matchers.isEmpty()) { failWithNonMatchingException(exception); } return; } catch (Throwable throwable) { failDueToUnexpectedException(throwable); } if (expectsThrowable()) { failDueToMissingException(); } } } }