Skip to content

Commit

Permalink
Print fully qualified class name when the simple names of arguments m…
Browse files Browse the repository at this point in the history
…atch (#2320)

Co-authored-by: thisisdexter <thesoundandthefury@protonmail.com>
  • Loading branch information
saurabh7248 and thisisdexter committed Jun 26, 2021
1 parent 2c62247 commit 1ad8235
Show file tree
Hide file tree
Showing 12 changed files with 292 additions and 15 deletions.
Expand Up @@ -9,18 +9,25 @@
* When ArgumentMatcher fails, chance is that the actual object has the same output of toString() than
* the wanted object. This looks weird when failures are reported.
* Therefore when matcher fails but toString() yields the same outputs,
* we will try to use the {@link #toStringWithType()} method.
* we will try to use the {@link #toStringWithType(String)} method.
*/
public interface ContainsExtraTypeInfo {

/**
* @param className - name of the class to be printed in description
* Returns more verbose description of the object which include type information
*/
String toStringWithType();
String toStringWithType(String className);

/**
* Checks if target target has matching type.
* If the type matches, there is no point in rendering result from {@link #toStringWithType()}
* If the type matches, there is no point in rendering result from {@link #toStringWithType(String)}
*/
boolean typeMatches(Object target);

/**
*
* @return Returns the wanted argument
*/
Object getWanted();
}
7 changes: 4 additions & 3 deletions src/main/java/org/mockito/internal/matchers/Equals.java
Expand Up @@ -31,7 +31,8 @@ private String describe(Object object) {
return ValuePrinter.print(object);
}

protected final Object getWanted() {
@Override
public final Object getWanted() {
return wanted;
}

Expand All @@ -51,8 +52,8 @@ public int hashCode() {
}

@Override
public String toStringWithType() {
return "(" + wanted.getClass().getSimpleName() + ") " + describe(wanted);
public String toStringWithType(String className) {
return "(" + className + ") " + describe(wanted);
}

@Override
Expand Down
Expand Up @@ -30,8 +30,25 @@ private Iterator<FormattedText> applyPrintSettings(
List<FormattedText> out = new LinkedList<>();
int i = 0;
for (final ArgumentMatcher matcher : matchers) {
if (matcher instanceof ContainsExtraTypeInfo && printSettings.extraTypeInfoFor(i)) {
out.add(new FormattedText(((ContainsExtraTypeInfo) matcher).toStringWithType()));
if (matcher instanceof ContainsExtraTypeInfo) {
ContainsExtraTypeInfo typeInfoMatcher = (ContainsExtraTypeInfo) matcher;
Object wanted = typeInfoMatcher.getWanted();
String simpleNameOfArgument =
wanted != null ? wanted.getClass().getSimpleName() : "";
String fullyQualifiedClassName =
wanted != null ? wanted.getClass().getCanonicalName() : "";

if (printSettings.extraTypeInfoFor(i)) {
out.add(
new FormattedText(
typeInfoMatcher.toStringWithType(simpleNameOfArgument)));
} else if (printSettings.fullyQualifiedNameFor(simpleNameOfArgument)) {
out.add(
new FormattedText(
typeInfoMatcher.toStringWithType(fullyQualifiedClassName)));
} else {
out.add(new FormattedText(MatcherToString.toString(matcher)));
}
} else {
out.add(new FormattedText(MatcherToString.toString(matcher)));
}
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/org/mockito/internal/reporting/PrintSettings.java
Expand Up @@ -5,8 +5,10 @@
package org.mockito.internal.reporting;

import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.mockito.ArgumentMatcher;
import org.mockito.internal.matchers.text.MatchersPrinter;
Expand All @@ -19,6 +21,7 @@ public class PrintSettings {
public static final int MAX_LINE_LENGTH = 45;
private boolean multiline;
private List<Integer> withTypeInfo = new LinkedList<>();
private Set<String> withFullyQualifiedName = Collections.emptySet();

public void setMultiline(boolean multiline) {
this.multiline = multiline;
Expand All @@ -38,10 +41,18 @@ public boolean extraTypeInfoFor(int argumentIndex) {
return withTypeInfo.contains(argumentIndex);
}

public boolean fullyQualifiedNameFor(String simpleClassName) {
return withFullyQualifiedName.contains(simpleClassName);
}

public void setMatchersToBeDescribedWithExtraTypeInfo(Integer[] indexesOfMatchers) {
this.withTypeInfo = Arrays.asList(indexesOfMatchers);
}

public void setMatchersToBeDescribedWithFullName(Set<String> indexesOfMatchers) {
this.withFullyQualifiedName = indexesOfMatchers;
}

public String print(List<ArgumentMatcher> matchers, Invocation invocation) {
MatchersPrinter matchersPrinter = new MatchersPrinter();
String qualifiedName =
Expand Down
Expand Up @@ -7,6 +7,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import org.mockito.invocation.Invocation;
import org.mockito.invocation.MatchableInvocation;
Expand All @@ -28,17 +29,20 @@ public SmartPrinter(
this(
wanted,
Collections.singletonList(actual),
indexesOfMatchersToBeDescribedWithExtraTypeInfo);
indexesOfMatchersToBeDescribedWithExtraTypeInfo,
Collections.emptySet());
}

public SmartPrinter(
MatchableInvocation wanted,
List<Invocation> allActualInvocations,
Integer... indexesOfMatchersToBeDescribedWithExtraTypeInfo) {
Integer[] indexesOfMatchersToBeDescribedWithExtraTypeInfo,
Set<String> classNamesToBeDescribedWithFullName) {
PrintSettings printSettings = new PrintSettings();
printSettings.setMultiline(isMultiLine(wanted, allActualInvocations));
printSettings.setMatchersToBeDescribedWithExtraTypeInfo(
indexesOfMatchersToBeDescribedWithExtraTypeInfo);
printSettings.setMatchersToBeDescribedWithFullName(classNamesToBeDescribedWithFullName);

this.wanted = printSettings.print(wanted);

Expand Down
Expand Up @@ -4,8 +4,13 @@
*/
package org.mockito.internal.verification.argumentmatching;

import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import org.mockito.ArgumentMatcher;
import org.mockito.internal.matchers.ContainsExtraTypeInfo;
Expand Down Expand Up @@ -48,4 +53,37 @@ private static boolean safelyMatches(ArgumentMatcher m, Object arg) {
private static boolean toStringEquals(ArgumentMatcher m, Object arg) {
return m.toString().equals(String.valueOf(arg));
}

/**
* Suspiciously not matching arguments are those that don't match, and the classes have same simple name.
*/
public static Set<String> getNotMatchingArgsWithSameName(
List<ArgumentMatcher> matchers, Object[] arguments) {
Map<String, Set<String>> classesHavingSameName = new HashMap<>();
for (ArgumentMatcher m : matchers) {
if (m instanceof ContainsExtraTypeInfo) {
Object wanted = ((ContainsExtraTypeInfo) m).getWanted();
if (wanted == null) {
continue;
}
Class wantedClass = wanted.getClass();
classesHavingSameName
.computeIfAbsent(wantedClass.getSimpleName(), className -> new HashSet<>())
.add(wantedClass.getCanonicalName());
}
}
for (Object argument : arguments) {
if (argument == null) {
continue;
}
Class wantedClass = argument.getClass();
classesHavingSameName
.computeIfAbsent(wantedClass.getSimpleName(), className -> new HashSet<>())
.add(wantedClass.getCanonicalName());
}
return classesHavingSameName.entrySet().stream()
.filter(classEntry -> classEntry.getValue().size() > 1)
.map(classEntry -> classEntry.getKey())
.collect(Collectors.toSet());
}
}
Expand Up @@ -11,9 +11,11 @@
import static org.mockito.internal.invocation.InvocationsFinder.findInvocations;
import static org.mockito.internal.invocation.InvocationsFinder.findPreviousVerifiedInOrder;
import static org.mockito.internal.invocation.InvocationsFinder.findSimilarInvocation;
import static org.mockito.internal.verification.argumentmatching.ArgumentMatchingTool.getNotMatchingArgsWithSameName;
import static org.mockito.internal.verification.argumentmatching.ArgumentMatchingTool.getSuspiciouslyNotMatchingArgsIndexes;

import java.util.List;
import java.util.Set;

import org.mockito.internal.reporting.SmartPrinter;
import org.mockito.internal.util.collections.ListUtil;
Expand Down Expand Up @@ -41,7 +43,11 @@ public static void checkMissingInvocation(

Integer[] indexesOfSuspiciousArgs =
getSuspiciouslyNotMatchingArgsIndexes(wanted.getMatchers(), similar.getArguments());
SmartPrinter smartPrinter = new SmartPrinter(wanted, invocations, indexesOfSuspiciousArgs);
Set<String> classesWithSameSimpleName =
getNotMatchingArgsWithSameName(wanted.getMatchers(), similar.getArguments());
SmartPrinter smartPrinter =
new SmartPrinter(
wanted, invocations, indexesOfSuspiciousArgs, classesWithSameSimpleName);
List<Location> actualLocations =
ListUtil.convert(
invocations,
Expand Down
6 changes: 3 additions & 3 deletions src/test/java/org/mockito/internal/matchers/EqualsTest.java
Expand Up @@ -28,21 +28,21 @@ public void shouldArraysBeEqual() {

@Test
public void shouldDescribeWithExtraTypeInfo() throws Exception {
String descStr = new Equals(100).toStringWithType();
String descStr = new Equals(100).toStringWithType(Integer.class.getSimpleName());

assertEquals("(Integer) 100", descStr);
}

@Test
public void shouldDescribeWithExtraTypeInfoOfLong() throws Exception {
String descStr = new Equals(100L).toStringWithType();
String descStr = new Equals(100L).toStringWithType(Long.class.getSimpleName());

assertEquals("(Long) 100L", descStr);
}

@Test
public void shouldDescribeWithTypeOfString() throws Exception {
String descStr = new Equals("x").toStringWithType();
String descStr = new Equals("x").toStringWithType(String.class.getSimpleName());

assertEquals("(String) \"x\"", descStr);
}
Expand Down
Expand Up @@ -111,14 +111,19 @@ public boolean matches(String item) {
}

@Override
public String toStringWithType() {
public String toStringWithType(String className) {
return "";
}

@Override
public boolean typeMatches(Object target) {
return true;
}

@Override
public Object getWanted() {
return "";
}
}

// given
Expand Down
48 changes: 48 additions & 0 deletions src/test/java/org/mockitousage/IMethods.java
Expand Up @@ -238,4 +238,52 @@ String simpleMethod(
String forObject(Object object);

<T> String genericToString(T arg);

void overloadedMethodWithSameClassNameArguments(java.sql.Date javaDate, Date date);

void overloadedMethodWithSameClassNameArguments(Date date, java.sql.Date javaDate);

void overloadedMethodWithDifferentClassNameArguments(String String, Integer i);

void overloadedMethodWithDifferentClassNameArguments(Integer i, String string);

void overloadedMethodWithSameClassNameArguments(
java.sql.Date javaDate, String string, Date date);

void overloadedMethodWithSameClassNameArguments(
Date date, String string, java.sql.Date javaDate);

/**
* Using this class to test cases where two classes have same simple name
*/
public static class Date {

private int value;

public Date(int value) {
this.value = value;
}

@Override
public String toString() {
return String.valueOf(value);
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Date date = (Date) o;
return value == date.value;
}

@Override
public int hashCode() {
return Objects.hash(value);
}
}
}
20 changes: 20 additions & 0 deletions src/test/java/org/mockitousage/MethodsImpl.java
Expand Up @@ -448,4 +448,24 @@ public Void voidReturningMethod() {
public <T> String genericToString(T arg) {
return null;
}

@Override
public void overloadedMethodWithSameClassNameArguments(java.sql.Date javaDate, Date date) {}

@Override
public void overloadedMethodWithSameClassNameArguments(Date date, java.sql.Date javaDate) {}

@Override
public void overloadedMethodWithDifferentClassNameArguments(String string, Integer i) {}

@Override
public void overloadedMethodWithDifferentClassNameArguments(Integer i, String string) {}

@Override
public void overloadedMethodWithSameClassNameArguments(
java.sql.Date javaDate, String string, Date date) {}

@Override
public void overloadedMethodWithSameClassNameArguments(
Date date, String string, java.sql.Date javaDate) {}
}

0 comments on commit 1ad8235

Please sign in to comment.