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 }