Skip to content

Commit

Permalink
Add unequal arguments positions in failed verification message (mocki…
Browse files Browse the repository at this point in the history
  • Loading branch information
mathieufortin01 committed Dec 22, 2022
1 parent f61d40d commit 40e4f6c
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 12 deletions.
60 changes: 56 additions & 4 deletions src/main/java/org/mockito/internal/exceptions/Reporter.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,29 @@

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import org.mockito.exceptions.base.MockitoAssertionError;
import org.mockito.exceptions.base.MockitoException;
import org.mockito.exceptions.base.MockitoInitializationException;
import org.mockito.exceptions.misusing.*;
import org.mockito.exceptions.misusing.CannotStubVoidMethodWithReturnValue;
import org.mockito.exceptions.misusing.CannotVerifyStubOnlyMock;
import org.mockito.exceptions.misusing.FriendlyReminderException;
import org.mockito.exceptions.misusing.InjectMocksException;
import org.mockito.exceptions.misusing.InvalidUseOfMatchersException;
import org.mockito.exceptions.misusing.MissingMethodInvocationException;
import org.mockito.exceptions.misusing.NotAMockException;
import org.mockito.exceptions.misusing.NullInsteadOfMockException;
import org.mockito.exceptions.misusing.PotentialStubbingProblem;
import org.mockito.exceptions.misusing.RedundantListenerException;
import org.mockito.exceptions.misusing.UnfinishedMockingSessionException;
import org.mockito.exceptions.misusing.UnfinishedStubbingException;
import org.mockito.exceptions.misusing.UnfinishedVerificationException;
import org.mockito.exceptions.misusing.UnnecessaryStubbingException;
import org.mockito.exceptions.misusing.WrongTypeOfReturnValue;
import org.mockito.exceptions.verification.MoreThanAllowedActualInvocations;
import org.mockito.exceptions.verification.NeverWantedButInvoked;
import org.mockito.exceptions.verification.NoInteractionsWanted;
Expand All @@ -28,10 +46,12 @@
import org.mockito.internal.junit.ExceptionFactory;
import org.mockito.internal.matchers.LocalizedMatcher;
import org.mockito.internal.util.MockUtil;
import org.mockito.internal.verification.argumentmatching.ArgumentMatchingTool;
import org.mockito.invocation.DescribedInvocation;
import org.mockito.invocation.Invocation;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.invocation.Location;
import org.mockito.invocation.MatchableInvocation;
import org.mockito.listeners.InvocationListener;
import org.mockito.mock.SerializableMode;

Expand Down Expand Up @@ -308,7 +328,11 @@ private static Object locationsOf(Collection<LocalizedMatcher> matchers) {
}

public static AssertionError argumentsAreDifferent(
String wanted, List<String> actualCalls, List<Location> actualLocations) {
Invocation actualInvocation,
MatchableInvocation matchableInvocation,
String wanted,
List<String> actualCalls,
List<Location> actualLocations) {
if (actualCalls == null
|| actualLocations == null
|| actualCalls.size() != actualLocations.size()) {
Expand All @@ -324,7 +348,11 @@ public static AssertionError argumentsAreDifferent(
.append("\n")
.append(LocationFactory.create())
.append("\n")
.append("Actual invocations have different arguments:\n");
.append("Actual invocations have different arguments");

appendNotMatchingPositions(actualInvocation, matchableInvocation, messageBuilder);

messageBuilder.append(":\n");

for (int i = 0; i < actualCalls.size(); i++) {
actualBuilder.append(actualCalls.get(i)).append("\n");
Expand All @@ -340,6 +368,30 @@ public static AssertionError argumentsAreDifferent(
messageBuilder.toString(), wanted, actualBuilder.toString());
}

/*
* Will append the non matching positions only if there are more than 1 arguments to the method.
*/
private static void appendNotMatchingPositions(
Invocation actualInvocation,
MatchableInvocation matchableInvocation,
StringBuilder messageBuilder) {
Object[] args = actualInvocation.getArguments();
if (args.length <= 1) {
return;
}

List<Integer> indexes =
ArgumentMatchingTool.getNotMatchingArgsIndexes(
matchableInvocation.getMatchers(), actualInvocation.getArguments());

if (!indexes.isEmpty()) {
messageBuilder
.append(" at ")
.append(indexes.size() == 1 ? "position " : "positions ")
.append(indexes);
}
}

public static MockitoAssertionError wantedButNotInvoked(DescribedInvocation wanted) {
return new WantedButNotInvoked(createWantedButNotInvokedMessage(wanted));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
*/
package org.mockito.internal.verification.argumentmatching;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
Expand All @@ -15,7 +17,7 @@
import org.mockito.ArgumentMatcher;
import org.mockito.internal.matchers.ContainsExtraTypeInfo;

@SuppressWarnings("unchecked")
@SuppressWarnings("rawtypes")
public class ArgumentMatchingTool {

private ArgumentMatchingTool() {}
Expand All @@ -42,6 +44,28 @@ && toStringEquals(m, arguments[i])
return suspicious.toArray(new Integer[0]);
}

/**
* Returns indexes of arguments not matching the provided matchers.
*/
public static List<Integer> getNotMatchingArgsIndexes(
List<ArgumentMatcher> matchers, Object[] arguments) {
if (matchers.size() != arguments.length) {
return Collections.emptyList();
}

List<Integer> nonMatching = new ArrayList<>();
int i = 0;
for (ArgumentMatcher m : matchers) {
if (!safelyMatches(m, arguments[i])) {
nonMatching.add(i);
}

i++;
}

return nonMatching;
}

private static boolean safelyMatches(ArgumentMatcher m, Object arg) {
try {
return m.matches(arg);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,11 @@ public static void checkMissingInvocation(
invocations.stream().map(Invocation::getLocation).collect(Collectors.toList());

throw argumentsAreDifferent(
smartPrinter.getWanted(), smartPrinter.getActuals(), actualLocations);
similar,
wanted,
smartPrinter.getWanted(),
smartPrinter.getActuals(),
actualLocations);
}

public static void checkMissingInvocation(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import org.mockito.internal.matchers.Equals;
import org.mockitoutil.TestBase;

@SuppressWarnings({"unchecked", "serial"})
@SuppressWarnings({"rawtypes", "unchecked", "serial"})
public class ArgumentMatchingToolTest extends TestBase {

@Test
Expand Down Expand Up @@ -97,7 +97,6 @@ public void shouldWorkFineWhenGivenArgIsNull() {
}

@Test
@SuppressWarnings("rawtypes")
public void shouldUseMatchersSafely() {
// This matcher is evil cause typeMatches(Object) returns true for every passed type but
// matches(T)
Expand Down Expand Up @@ -137,4 +136,49 @@ public Object getWanted() {
// then
assertEquals(0, suspicious.length);
}

@Test
public void shouldNotFindNonMatchingIndexesWhenEveryArgsMatch() {
String arg1Value = "arg1";
Integer arg2Value = 2222;

Equals arg1 = new Equals(arg1Value);
Equals arg2 = new Equals(arg2Value);

List<Integer> indexes =
ArgumentMatchingTool.getNotMatchingArgsIndexes(
Arrays.asList(arg1, arg2), new Object[] {arg1Value, arg2Value});

assertEquals(Arrays.asList(), indexes);
}

@Test
public void shouldFindNonMatchingIndexesWhenSingleArgDoesNotMatch() {
String arg1Value = "arg1";
Integer arg2Value = 2222;

Equals arg1 = new Equals(arg1Value);
Equals arg2 = new Equals(1111);

List<Integer> indexes =
ArgumentMatchingTool.getNotMatchingArgsIndexes(
Arrays.asList(arg1, arg2), new Object[] {arg1Value, arg2Value});

assertEquals(Arrays.asList(1), indexes);
}

@Test
public void shouldFindNonMatchingIndexesWhenMultiArgsDoNotMatch() {
String arg1Value = "arg1";
Integer arg2Value = 2222;

Equals arg1 = new Equals("differs");
Equals arg2 = new Equals(1111);

List<Integer> indexes =
ArgumentMatchingTool.getNotMatchingArgsIndexes(
Arrays.asList(arg1, arg2), new Object[] {arg1Value, arg2Value});

assertEquals(Arrays.asList(0, 1), indexes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,76 @@ public void shouldReportUsingInvocationDescription() {
"mock.intArgumentMethod(MyCoolPrint(1111));");
}

@Test
public void shouldSpecifyPosition0WhenWantedInvocationDiffersFromActual() {
wanted = buildMultiArgsMethod().args("arg1", 2222).toInvocationMatcher();
invocations = singletonList(buildMultiArgsMethod().args("differs", 2222).toInvocation());

assertThatThrownBy(
() -> {
MissingInvocationChecker.checkMissingInvocation(invocations, wanted);
})
.isInstanceOf(ArgumentsAreDifferent.class)
.hasMessageContainingAll(
"Argument(s) are different! Wanted:",
"mock.simpleMethod(\"arg1\", 2222);",
"Actual invocations have different arguments at position [0]:",
"mock.simpleMethod(\"differs\", 2222);");
}

@Test
public void shouldSpecifyPosition1WhenWantedInvocationDiffersFromActual() {
wanted = buildMultiArgsMethod().args("arg1", 2222).toInvocationMatcher();
invocations = singletonList(buildMultiArgsMethod().args("arg1", 1111).toInvocation());

assertThatThrownBy(
() -> {
MissingInvocationChecker.checkMissingInvocation(invocations, wanted);
})
.isInstanceOf(ArgumentsAreDifferent.class)
.hasMessageContainingAll(
"Argument(s) are different! Wanted:",
"mock.simpleMethod(\"arg1\", 2222);",
"Actual invocations have different arguments at position [1]:",
"mock.simpleMethod(\"arg1\", 1111);");
}

@Test
public void shouldSpecifyPosition0And1WhenWantedInvocationDiffersFromActual() {
wanted = buildMultiArgsMethod().args("arg1", 2222).toInvocationMatcher();
invocations = singletonList(buildMultiArgsMethod().args("differs", 1111).toInvocation());

assertThatThrownBy(
() -> {
MissingInvocationChecker.checkMissingInvocation(invocations, wanted);
})
.isInstanceOf(ArgumentsAreDifferent.class)
.hasMessageContainingAll(
"Argument(s) are different! Wanted:",
"mock.simpleMethod(\"arg1\", 2222);",
"Actual invocations have different arguments at positions [0, 1]:",
"mock.simpleMethod(\"differs\", 1111);");
}

@Test
public void shouldNotSpecifyPositionWhenWantedSingleArgInvocationSiffersFromActual() {
wanted = buildIntArgMethod(new CustomInvocationBuilder()).arg(2222).toInvocationMatcher();
invocations =
singletonList(
buildIntArgMethod(new CustomInvocationBuilder()).arg(1111).toInvocation());

assertThatThrownBy(
() -> {
MissingInvocationChecker.checkMissingInvocation(invocations, wanted);
})
.isInstanceOf(ArgumentsAreDifferent.class)
.hasMessageContainingAll(
"Argument(s) are different! Wanted:",
"mock.intArgumentMethod(MyCoolPrint(2222));",
"Actual invocations have different arguments:",
"mock.intArgumentMethod(MyCoolPrint(1111));");
}

private InvocationBuilder buildIntArgMethod(InvocationBuilder invocationBuilder) {
return invocationBuilder.mock(mock).method("intArgumentMethod").argTypes(int.class);
}
Expand All @@ -107,6 +177,13 @@ private InvocationBuilder buildDifferentMethod() {
return new InvocationBuilder().mock(mock).differentMethod();
}

private InvocationBuilder buildMultiArgsMethod() {
return new InvocationBuilder()
.mock(mock)
.method("simpleMethod")
.argTypes(String.class, Integer.class);
}

static class CustomInvocationBuilder extends InvocationBuilder {
@Override
protected Invocation createInvocation(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public void should_print_actual_and_wanted_in_line() {

String actual =
"\n"
+ "Actual invocations have different arguments:"
+ "Actual invocations have different arguments at position [1]:"
+ "\n"
+ "iMethods.varargs(1, 2);";

Expand Down Expand Up @@ -397,7 +397,7 @@ public void should_print_fully_qualified_name_when_arguments_classes_have_same_s
String actual =
String.format(
"\n"
+ "Actual invocations have different arguments:"
+ "Actual invocations have different arguments at positions [0, 1]:"
+ "\n"
+ "iMethods.overloadedMethodWithSameClassNameArguments("
+ "\n"
Expand Down Expand Up @@ -435,7 +435,7 @@ public void should_print_fully_qualified_name_when_arguments_classes_have_same_s

String actual =
"\n"
+ "Actual invocations have different arguments:"
+ "Actual invocations have different arguments at positions [0, 1]:"
+ "\n"
+ "iMethods.overloadedMethodWithDifferentClassNameArguments("
+ "\n"
Expand Down Expand Up @@ -483,7 +483,7 @@ public void should_print_fully_qualified_name_when_arguments_classes_have_same_s
String actual =
String.format(
"\n"
+ "Actual invocations have different arguments:"
+ "Actual invocations have different arguments at positions [0, 2]:"
+ "\n"
+ "iMethods.overloadedMethodWithSameClassNameArguments("
+ "\n"
Expand Down

0 comments on commit 40e4f6c

Please sign in to comment.