001 /* 002 * Created on Aug 5, 2010 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 005 * the License. You may obtain a copy of the License at 006 * 007 * http://www.apache.org/licenses/LICENSE-2.0 008 * 009 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 010 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 011 * specific language governing permissions and limitations under the License. 012 * 013 * Copyright @2010-2011 the original author or authors. 014 */ 015 package org.fest.assertions.error; 016 017 import static java.lang.Integer.toHexString; 018 019 import static org.fest.util.Arrays.array; 020 import static org.fest.util.Objects.*; 021 import static org.fest.util.ToString.toStringOf; 022 023 import org.fest.assertions.description.Description; 024 import org.fest.assertions.internal.Failures; 025 import org.fest.util.ComparatorBasedComparisonStrategy; 026 import org.fest.util.ComparisonStrategy; 027 import org.fest.util.StandardComparisonStrategy; 028 import org.fest.util.VisibleForTesting; 029 030 /** 031 * Creates an <code>{@link AssertionError}</code> indicating that an assertion that verifies that two objects are equal 032 * failed. 033 * <p> 034 * The built {@link AssertionError}'s message differentiates {@link #actual} and {@link #expected} description if their 035 * string representation are the same (e.g. 42 float and 42 double). It also mentions the comparator in case of a custom 036 * comparator is used (instead of equals method). 037 * 038 * @author Alex Ruiz 039 * @author Yvonne Wang 040 * @author Joel Costigliola 041 */ 042 public class ShouldBeEqual implements AssertionErrorFactory { 043 044 private static final String EXPECTED_BUT_WAS_MESSAGE = "expected:<%s> but was:<%s>"; 045 private static final String EXPECTED_BUT_WAS_MESSAGE_USING_COMPARATOR = "Expecting actual:<%s> to be equal to <%s>%s but was not."; 046 047 private static final Class<?>[] MSG_ARG_TYPES = new Class<?>[] { String.class, String.class, String.class }; 048 049 @VisibleForTesting 050 ConstructorInvoker constructorInvoker = new ConstructorInvoker(); 051 @VisibleForTesting 052 MessageFormatter messageFormatter = MessageFormatter.instance(); 053 @VisibleForTesting 054 DescriptionFormatter descriptionFormatter = DescriptionFormatter.instance(); 055 056 protected final Object actual; 057 protected final Object expected; 058 private ComparisonStrategy comparisonStrategy; 059 060 /** 061 * Creates a new <code>{@link ShouldBeEqual}</code>. 062 * @param actual the actual value in the failed assertion. 063 * @param expected the expected value in the failed assertion. 064 * @return the created {@code AssertionErrorFactory}. 065 */ 066 public static AssertionErrorFactory shouldBeEqual(Object actual, Object expected) { 067 return new ShouldBeEqual(actual, expected, StandardComparisonStrategy.instance()); 068 } 069 070 /** 071 * Creates a new <code>{@link ShouldBeEqual}</code>. 072 * @param actual the actual value in the failed assertion. 073 * @param expected the expected value in the failed assertion. 074 * @param comparisonStrategy the {@link ComparisonStrategy} used to compare actual with expected. 075 * @return the created {@code AssertionErrorFactory}. 076 */ 077 public static AssertionErrorFactory shouldBeEqual(Object actual, Object expected, 078 ComparisonStrategy comparisonStrategy) { 079 return new ShouldBeEqual(actual, expected, comparisonStrategy); 080 } 081 082 @VisibleForTesting 083 ShouldBeEqual(Object actual, Object expected, ComparisonStrategy comparisonStrategy) { 084 this.actual = actual; 085 this.expected = expected; 086 this.comparisonStrategy = comparisonStrategy; 087 } 088 089 /** 090 * Creates an <code>{@link AssertionError}</code> indicating that an assertion that verifies that two objects are 091 * equal failed.<br> 092 * The <code>{@link AssertionError}</code> message is built so that it differentiates {@link #actual} and 093 * {@link #expected} description in case their string representation are the same (like 42 float and 42 double). 094 * <p> 095 * If JUnit 4 is in the classpath and the description is standard (no comparator was used and {@link #actual} and 096 * {@link #expected} string representation were differents), this method will instead create a 097 * org.junit.ComparisonFailure that highlights the difference(s) between the expected and actual objects. 098 * </p> 099 * {@link AssertionError} stack trace won't show Fest related elements if {@link Failures} is configured to filter 100 * them (see {@link Failures#setRemoveFestRelatedElementsFromStackTrace(boolean)}). 101 * 102 * @param description the description of the failed assertion. 103 * @return the created {@code AssertionError}. 104 */ 105 public AssertionError newAssertionError(Description description) { 106 if (actualAndExpectedHaveSameStringRepresentation()) { 107 // Example : actual = 42f and expected = 42d gives actual : "42" and expected : "42" and 108 // JUnit 4 manages this case even worst, it will output something like : 109 // "java.lang.String expected:java.lang.String<42.0> but was: java.lang.String<42.0>" 110 // which does not solve the problem and makes things even more confusing since we lost the fact that 42 was a 111 // float or a double, it is then better to built our own description, with the drawback of not using a 112 // ComparisonFailure (which looks nice in eclipse) 113 return Failures.instance().failure(defaultDetailedErrorMessage(description)); 114 } 115 // if comparison strategy was based on a custom comparator, we build the assertion error message, the result is 116 // better than the JUnit ComparisonFailure we could build (that would not mention the comparator). 117 if (isJUnitComparisonFailureRelevant()) { 118 // try to build a JUnit ComparisonFailure that offers a nice IDE integration. 119 AssertionError error = comparisonFailure(description); 120 if (error != null) { return error; } 121 } 122 // No JUnit in the classpath => fall back to default error message. 123 return Failures.instance().failure(defaultErrorMessage(description)); 124 } 125 126 /** 127 * Tells {@link #newAssertionError(Description)} if it should try a build a {@link ComparisonFailure}.<br> 128 * Returns <code>true</code> as we try in this class (may not be the case in subclasses). 129 * @return <code>true</code> 130 */ 131 private boolean isJUnitComparisonFailureRelevant() { 132 // to add comparator description, we can't rely on JUnit ComparisonFailure since it will ignore it. 133 if (comparisonStrategy instanceof ComparatorBasedComparisonStrategy) return false; 134 // we don't care to mention the strategy used => trying to build a JUnit comparison failure is relevant. 135 return true; 136 } 137 138 private boolean actualAndExpectedHaveSameStringRepresentation() { 139 return areEqual(toStringOf(actual), toStringOf(expected)); 140 } 141 142 /** 143 * Builds and returns an error message from description using {@link #expected} and {@link #actual} basic 144 * representation. 145 * @param description the {@link Description} used to build the returned error message 146 * @return the error message from description using {@link #expected} and {@link #actual} basic representation. 147 */ 148 private String defaultErrorMessage(Description description) { 149 if (comparisonStrategy instanceof ComparatorBasedComparisonStrategy) 150 return messageFormatter.format(description, EXPECTED_BUT_WAS_MESSAGE_USING_COMPARATOR, actual, expected, 151 comparisonStrategy); 152 return messageFormatter.format(description, EXPECTED_BUT_WAS_MESSAGE, expected, actual); 153 } 154 155 /** 156 * Builds and returns an error message from description using {@link #expectedDetailedToString()} and 157 * {@link #detailedActual()} detailed representation. 158 * @param description the {@link Description} used to build the returned error message 159 * @return the error message from description using {@link #detailedExpected()} and {@link #detailedActual()} 160 * <b>detailed</b> representation. 161 */ 162 private String defaultDetailedErrorMessage(Description description) { 163 if (comparisonStrategy instanceof ComparatorBasedComparisonStrategy) 164 return messageFormatter.format(description, EXPECTED_BUT_WAS_MESSAGE_USING_COMPARATOR, detailedActual(), 165 detailedExpected(), comparisonStrategy); 166 return messageFormatter.format(description, EXPECTED_BUT_WAS_MESSAGE, detailedExpected(), detailedActual()); 167 } 168 169 private AssertionError comparisonFailure(Description description) { 170 try { 171 AssertionError comparisonFailure = newComparisonFailure(descriptionFormatter.format(description).trim()); 172 Failures.instance().removeFestRelatedElementsFromStackTraceIfNeeded(comparisonFailure); 173 return comparisonFailure; 174 } catch (Throwable e) { 175 return null; 176 } 177 } 178 179 private AssertionError newComparisonFailure(String description) throws Exception { 180 Object o = constructorInvoker.newInstance("org.junit.ComparisonFailure", MSG_ARG_TYPES, msgArgs(description)); 181 if (o instanceof AssertionError) return (AssertionError) o; 182 return null; 183 } 184 185 private Object[] msgArgs(String description) { 186 return array(description, toStringOf(expected), toStringOf(actual)); 187 } 188 189 private static String detailedToStringOf(Object obj) { 190 return toStringOf(obj) + " (" + obj.getClass().getSimpleName() + "@" + toHexString(obj.hashCode()) + ")"; 191 } 192 193 private String detailedActual() { 194 return detailedToStringOf(actual); 195 } 196 197 private String detailedExpected() { 198 return detailedToStringOf(expected); 199 } 200 201 @Override 202 public boolean equals(Object o) { 203 if (this == o) return true; 204 if (o == null) return false; 205 if (getClass() != o.getClass()) return false; 206 ShouldBeEqual other = (ShouldBeEqual) o; 207 if (!areEqual(actual, other.actual)) return false; 208 return areEqual(expected, other.expected); 209 } 210 211 @Override 212 public int hashCode() { 213 int result = 1; 214 result = HASH_CODE_PRIME * result + hashCodeFor(actual); 215 result = HASH_CODE_PRIME * result + hashCodeFor(expected); 216 return result; 217 } 218 }