Skip to content

Commit

Permalink
hasMessage now fails with an AssertionFailedError if available and wi…
Browse files Browse the repository at this point in the history
…ll report actual and expected messages which can be visually compared in IDEs (the assertion error message is still the same).

If AssertionFailedError is not available, simply fallback to a simple AssertionError as previously.
  • Loading branch information
joel-costigliola committed Aug 13, 2018
1 parent 0547bfc commit 1e40b38
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 36 deletions.
55 changes: 55 additions & 0 deletions src/main/java/org/assertj/core/error/AssertionErrorCreator.java
@@ -0,0 +1,55 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* Copyright 2012-2018 the original author or authors.
*/
package org.assertj.core.error;

import static org.assertj.core.util.Arrays.array;

import java.util.Optional;

import org.assertj.core.util.VisibleForTesting;

// TODO deprecate AssertionErrorFactory
public class AssertionErrorCreator {

private static final Class<?>[] MSG_ARG_TYPES_FOR_ASSERTION_FAILED_ERROR = array(String.class, Object.class, Object.class);

@VisibleForTesting
static ConstructorInvoker constructorInvoker = new ConstructorInvoker();

public static AssertionError assertionError(String message, Object actual, Object expected) {
return assertionFailedError(message, actual, expected).orElse(assertionError(message));
}

private static Optional<AssertionError> assertionFailedError(String message, Object actual, Object expected) {
try {
Object o = constructorInvoker.newInstance("org.opentest4j.AssertionFailedError",
MSG_ARG_TYPES_FOR_ASSERTION_FAILED_ERROR,
message,
expected,
actual);
if (o instanceof AssertionError) {
AssertionError assertionError = (AssertionError) o;
return Optional.of(assertionError);
}
// TODO: single return
return Optional.empty();
} catch (Throwable e) {
return Optional.empty();
}
}

private static AssertionError assertionError(String message) {
return new AssertionError(message);
}

}
49 changes: 33 additions & 16 deletions src/main/java/org/assertj/core/internal/Failures.java
Expand Up @@ -13,6 +13,7 @@
package org.assertj.core.internal;

import static java.lang.String.format;
import static org.assertj.core.error.AssertionErrorCreator.assertionError;
import static org.assertj.core.util.Strings.isNullOrEmpty;

import java.lang.management.ManagementFactory;
Expand All @@ -30,7 +31,7 @@

/**
* Failure actions.
*
*
* @author Yvonne Wang
* @author Alex Ruiz
*/
Expand All @@ -47,7 +48,7 @@ public class Failures {

/**
* Returns the singleton instance of this class.
*
*
* @return the singleton instance of this class.
*/
public static Failures instance() {
Expand All @@ -61,15 +62,15 @@ public static Failures instance() {

/**
* Sets whether we remove elements related to AssertJ from assertion error stack trace.
*
*
* @param removeAssertJRelatedElementsFromStackTrace flag
*/
public void setRemoveAssertJRelatedElementsFromStackTrace(boolean removeAssertJRelatedElementsFromStackTrace) {
this.removeAssertJRelatedElementsFromStackTrace = removeAssertJRelatedElementsFromStackTrace;
}

/**
* Returns whether or not we remove elements related to AssertJ from assertion error stack trace.
/**
* Returns whether or not we remove elements related to AssertJ from assertion error stack trace.
* @return whether or not we remove elements related to AssertJ from assertion error stack trace.
*/
public boolean isRemoveAssertJRelatedElementsFromStackTrace() {
Expand All @@ -87,7 +88,7 @@ public boolean isRemoveAssertJRelatedElementsFromStackTrace() {
* <li>uses the given <code>{@link AssertionErrorFactory}</code> to create an <code>{@link AssertionError}</code>,
* prepending the value of <code>{@link AssertionInfo#description()}</code> to the error message</li>
* </ol>
*
*
* @param info contains information about the failed assertion.
* @param factory knows how to create {@code AssertionError}s.
* @return the created <code>{@link AssertionError}</code>.
Expand All @@ -108,20 +109,36 @@ public AssertionError failure(AssertionInfo info, AssertionErrorFactory factory)
* <code>{@link AssertionError}</code>, prepending the value of <code>{@link AssertionInfo#description()}</code> to
* the error message</li>
* </ol>
*
*
* @param info contains information about the failed assertion.
* @param message knows how to create detail messages for {@code AssertionError}s.
* @param messageFactory knows how to create detail messages for {@code AssertionError}s.
* @return the created <code>{@link AssertionError}</code>.
*/
public AssertionError failure(AssertionInfo info, ErrorMessageFactory message) {
public AssertionError failure(AssertionInfo info, ErrorMessageFactory messageFactory) {
AssertionError error = failureIfErrorMessageIsOverridden(info);
if (error != null) return error;
AssertionError assertionError = new AssertionError(message.create(info.description(), info.representation()));
AssertionError assertionError = new AssertionError(messageFactory.create(info.description(), info.representation()));
removeAssertJRelatedElementsFromStackTraceIfNeeded(assertionError);
printThreadDumpIfNeeded();
return assertionError;
}

public AssertionError failure(AssertionInfo info, ErrorMessageFactory messageFactory, Object actual, Object expected) {
String assertionErrorMessage = assertionErrorMessage(info, messageFactory);
AssertionError assertionError = assertionError(assertionErrorMessage, actual, expected);
removeAssertJRelatedElementsFromStackTraceIfNeeded(assertionError);
printThreadDumpIfNeeded();
return assertionError;
}

protected String assertionErrorMessage(AssertionInfo info, ErrorMessageFactory messageFactory) {
String overridingErrorMessage = info.overridingErrorMessage();
String message = isNullOrEmpty(overridingErrorMessage)
? messageFactory.create(info.description(), info.representation())
: MessageFormatter.instance().format(info.description(), info.representation(), overridingErrorMessage);
return message;
}

public AssertionError failureIfErrorMessageIsOverridden(AssertionInfo info) {
String overridingErrorMessage = info.overridingErrorMessage();
return isNullOrEmpty(overridingErrorMessage) ? null
Expand All @@ -134,7 +151,7 @@ public AssertionError failureIfErrorMessageIsOverridden(AssertionInfo info) {
* <p>
* It filters the AssertionError stack trace be default, to have full stack trace use
* {@link #setRemoveAssertJRelatedElementsFromStackTrace(boolean)}.
*
*
* @param message the message of the {@code AssertionError} to create.
* @return the created <code>{@link AssertionError}</code>.
*/
Expand All @@ -155,12 +172,12 @@ public AssertionError expectedThrowableNotThrown(Class<? extends Throwable> thro
return failure(format("%s should have been thrown", throwableClass.getSimpleName()));
}

private void printThreadDumpIfNeeded() {
public void printThreadDumpIfNeeded() {
if (printThreadDump) System.err.println(threadDumpDescription());
}

/**
* If is {@link #removeAssertJRelatedElementsFromStackTrace} is true, it filters the stack trace of the given {@link AssertionError}
* If is {@link #removeAssertJRelatedElementsFromStackTrace} is true, it filters the stack trace of the given {@link AssertionError}
* by removing stack trace elements related to AssertJ in order to get a more readable stack trace.
* <p>
* See example below :
Expand All @@ -185,9 +202,9 @@ private void printThreadDumpIfNeeded() {
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
at examples.StackTraceFilterExample.main(StackTraceFilterExample.java:20)</code></pre>
*
*
* Method is public because we need to call it from {@link ShouldBeEqual#newAssertionError(Description, org.assertj.core.presentation.Representation)} that is building a junit ComparisonFailure by reflection.
*
*
* @param assertionError the {@code AssertionError} to filter stack trace if option is set.
*/
public void removeAssertJRelatedElementsFromStackTraceIfNeeded(AssertionError assertionError) {
Expand All @@ -203,7 +220,7 @@ public void enablePrintThreadDump() {
printThreadDump = true;
}

private String threadDumpDescription() {
public static String threadDumpDescription() {
StringBuilder threadDumpDescription = new StringBuilder();
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(true, true);
Expand Down
32 changes: 16 additions & 16 deletions src/main/java/org/assertj/core/internal/Throwables.java
Expand Up @@ -35,7 +35,7 @@

/**
* Reusable assertions for <code>{@link Throwable}</code>s.
*
*
* @author Joel Costigliola
* @author Libor Ondrusek
*/
Expand All @@ -45,7 +45,7 @@ public class Throwables {

/**
* Returns the singleton instance of this class.
*
*
* @return the singleton instance of this class.
*/
public static Throwables instance() {
Expand All @@ -60,17 +60,17 @@ public static Throwables instance() {

/**
* Asserts that the given actual {@code Throwable} message is equal to the given one.
*
*
* @param info contains information about the assertion.
* @param actual the given {@code Throwable}.
* @param message the expected message.
* @param expectedMessage the expected message.
* @throws AssertionError if the actual {@code Throwable} is {@code null}.
* @throws AssertionError if the message of the actual {@code Throwable} is not equal to the given one.
*/
public void assertHasMessage(AssertionInfo info, Throwable actual, String message) {
public void assertHasMessage(AssertionInfo info, Throwable actual, String expectedMessage) {
assertNotNull(info, actual);
if (areEqual(actual.getMessage(), message)) return;
throw failures.failure(info, shouldHaveMessage(actual, message));
if (areEqual(actual.getMessage(), expectedMessage)) return;
throw failures.failure(info, shouldHaveMessage(actual, expectedMessage), actual.getMessage(), expectedMessage);
}

public void assertHasCause(AssertionInfo info, Throwable actual, Throwable expectedCause) {
Expand All @@ -88,7 +88,7 @@ public void assertHasCause(AssertionInfo info, Throwable actual, Throwable expec

/**
* Asserts that the actual {@code Throwable} does not have a cause.
*
*
* @param info contains information about the assertion.
* @param actual the given {@code Throwable}.
* @throws AssertionError if the actual {@code Throwable} is {@code null}.
Expand All @@ -103,7 +103,7 @@ public void assertHasNoCause(AssertionInfo info, Throwable actual) {

/**
* Asserts that the message of the actual {@code Throwable} starts with the given description.
*
*
* @param info contains information about the assertion.
* @param actual the given {@code Throwable}.
* @param description the description expected to start the actual {@code Throwable}'s message.
Expand All @@ -118,7 +118,7 @@ public void assertHasMessageStartingWith(AssertionInfo info, Throwable actual, S

/**
* Asserts that the message of the actual {@code Throwable} contains with the given description.
*
*
* @param info contains information about the assertion.
* @param actual the given {@code Throwable}.
* @param description the description expected to be contained in the actual {@code Throwable}'s message.
Expand All @@ -133,7 +133,7 @@ public void assertHasMessageContaining(AssertionInfo info, Throwable actual, Str

/**
* Asserts that the stack trace of the actual {@code Throwable} contains with the given description.
*
*
* @param info contains information about the assertion.
* @param actual the given {@code Throwable}.
* @param description the description expected to be contained in the actual {@code Throwable}'s stack trace.
Expand Down Expand Up @@ -166,7 +166,7 @@ public void assertHasMessageMatching(AssertionInfo info, Throwable actual, Strin

/**
* Asserts that the message of the actual {@code Throwable} ends with the given description.
*
*
* @param info contains information about the assertion.
* @param actual the given {@code Throwable}.
* @param description the description expected to end the actual {@code Throwable}'s message.
Expand All @@ -181,7 +181,7 @@ public void assertHasMessageEndingWith(AssertionInfo info, Throwable actual, Str

/**
* Assert that the cause of actual {@code Throwable} is an instance of the given type.
*
*
* @param info contains information about the assertion.
* @param actual the given {@code Throwable}.
* @param type the expected cause type.
Expand All @@ -199,7 +199,7 @@ public void assertHasCauseInstanceOf(AssertionInfo info, Throwable actual, Class

/**
* Assert that the cause of actual {@code Throwable} is <b>exactly</b> an instance of the given type.
*
*
* @param info contains information about the assertion.
* @param actual the given {@code Throwable}.
* @param type the expected cause type.
Expand All @@ -219,7 +219,7 @@ public void assertHasCauseExactlyInstanceOf(AssertionInfo info, Throwable actual

/**
* Assert that the root cause of actual {@code Throwable} is an instance of the given type.
*
*
* @param info contains information about the assertion.
* @param actual the given {@code Throwable}.
* @param type the expected cause type.
Expand All @@ -237,7 +237,7 @@ public void assertHasRootCauseInstanceOf(AssertionInfo info, Throwable actual, C

/**
* Assert that the root cause of actual {@code Throwable} is <b>exactly</b> an instance of the given type.
*
*
* @param info contains information about the assertion.
* @param actual the given {@code Throwable}.
* @param type the expected cause type.
Expand Down
Expand Up @@ -12,11 +12,11 @@
*/
package org.assertj.core.internal.throwables;

import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.fail;
import static org.assertj.core.error.ShouldHaveMessage.shouldHaveMessage;
import static org.assertj.core.test.TestData.someInfo;
import static org.assertj.core.util.FailureMessages.actualIsNull;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.fail;
import static org.mockito.Mockito.verify;

import org.assertj.core.api.AssertionInfo;
Expand All @@ -27,7 +27,7 @@

/**
* Tests for <code>{@link Throwables#assertHasMessage(AssertionInfo, Throwable, String)}</code>.
*
*
* @author Joel Costigliola
*/
public class Throwables_assertHasMessage_Test extends ThrowablesBaseTest {
Expand All @@ -50,7 +50,7 @@ public void should_fail_if_actual_has_not_expected_message() {
throwables.assertHasMessage(info, actual, "expected message");
fail("AssertionError expected");
} catch (AssertionError err) {
verify(failures).failure(info, shouldHaveMessage(actual, "expected message"));
verify(failures).failure(info, shouldHaveMessage(actual, "expected message"), "Throwable message", "expected message");
}
}
}

0 comments on commit 1e40b38

Please sign in to comment.