diff --git a/src/main/java/org/mockito/internal/stubbing/answers/InvocationInfo.java b/src/main/java/org/mockito/internal/stubbing/answers/InvocationInfo.java index 8bfffca2fe..ef204e2dee 100644 --- a/src/main/java/org/mockito/internal/stubbing/answers/InvocationInfo.java +++ b/src/main/java/org/mockito/internal/stubbing/answers/InvocationInfo.java @@ -6,6 +6,8 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.Objects; import org.mockito.internal.invocation.AbstractAwareMethod; import org.mockito.internal.util.MockUtil; @@ -25,6 +27,26 @@ public InvocationInfo(InvocationOnMock theInvocation) { } public boolean isValidException(Throwable throwable) { + if (isValidException(method, throwable)) { + return true; + } + + return Arrays.stream(method.getDeclaringClass().getInterfaces()) + .map(this::getMethodOrNull) + .filter(Objects::nonNull) + .anyMatch(parentMethod -> isValidException(parentMethod, throwable)); + } + + private Method getMethodOrNull(Class parent) { + try { + return parent.getMethod(method.getName(), method.getParameterTypes()); + } catch (NoSuchMethodException e) { + // ignore interfaces that doesn't have such a method + return null; + } + } + + private boolean isValidException(Method method, Throwable throwable) { Class[] exceptions = method.getExceptionTypes(); Class throwableClass = throwable.getClass(); for (Class exception : exceptions) { @@ -32,7 +54,6 @@ public boolean isValidException(Throwable throwable) { return true; } } - return false; } diff --git a/src/test/java/org/mockito/internal/invocation/InvocationBuilder.java b/src/test/java/org/mockito/internal/invocation/InvocationBuilder.java index 281d41b3c3..9900db9d98 100644 --- a/src/test/java/org/mockito/internal/invocation/InvocationBuilder.java +++ b/src/test/java/org/mockito/internal/invocation/InvocationBuilder.java @@ -30,6 +30,7 @@ public class InvocationBuilder { private int sequenceNumber = 0; private Object[] args = new Object[] {}; private Object mock = Mockito.mock(IMethods.class); + private Class mockClass = IMethods.class; private Method method; private boolean verified; private List> argTypes; @@ -57,7 +58,7 @@ public Invocation toInvocation() { try { method = - IMethods.class.getMethod( + mockClass.getMethod( methodName, argTypes.toArray(new Class[argTypes.size()])); } catch (Exception e) { throw new RuntimeException( @@ -115,6 +116,12 @@ public InvocationBuilder mock(Object mock) { return this; } + public InvocationBuilder mockClass(Class mockClass) { + this.mockClass = mockClass; + this.mock = mock(mockClass); + return this; + } + public InvocationBuilder method(Method method) { this.method = method; return this; diff --git a/src/test/java/org/mockito/internal/stubbing/answers/InvocationInfoTest.java b/src/test/java/org/mockito/internal/stubbing/answers/InvocationInfoTest.java index 3c9d96983d..c365710cdd 100644 --- a/src/test/java/org/mockito/internal/stubbing/answers/InvocationInfoTest.java +++ b/src/test/java/org/mockito/internal/stubbing/answers/InvocationInfoTest.java @@ -15,6 +15,7 @@ import org.mockito.internal.invocation.InvocationBuilder; import org.mockito.invocation.Invocation; import org.mockitousage.IMethods; +import org.mockitousage.MethodsImpl; public class InvocationInfoTest { @@ -29,6 +30,20 @@ public void should_know_valid_throwables() throws Exception { assertThat(info.isValidException(new CharacterCodingException())).isTrue(); } + @Test + public void should_mark_interface_overridden_exceptions_as_valid() { + // when + Invocation invocation = + new InvocationBuilder() + .method("throwsRemovedInSubclass") + .mockClass(MethodsImpl.class) + .toInvocation(); + InvocationInfo info = new InvocationInfo(invocation); + + // then + assertThat(info.isValidException(new CharacterCodingException())).isTrue(); + } + @Test public void should_know_valid_return_types() throws Exception { assertThat( diff --git a/src/test/java/org/mockitousage/IMethods.java b/src/test/java/org/mockitousage/IMethods.java index 12897422da..5d92dc1b13 100644 --- a/src/test/java/org/mockitousage/IMethods.java +++ b/src/test/java/org/mockitousage/IMethods.java @@ -159,6 +159,8 @@ String simpleMethod( String canThrowException() throws CharacterCodingException; + String throwsRemovedInSubclass() throws CharacterCodingException; + String oneArray(String[] array); void varargsString(int i, String... string); diff --git a/src/test/java/org/mockitousage/MethodsImpl.java b/src/test/java/org/mockitousage/MethodsImpl.java index bb69656712..a853e9f04c 100644 --- a/src/test/java/org/mockitousage/MethodsImpl.java +++ b/src/test/java/org/mockitousage/MethodsImpl.java @@ -303,6 +303,11 @@ public String canThrowException() throws CharacterCodingException { return null; } + @Override + public String throwsRemovedInSubclass() { + return null; + } + public String oneArray(String[] array) { return null; } diff --git a/src/test/java/org/mockitousage/junitrunner/TestClassInitializationTest.java b/src/test/java/org/mockitousage/junitrunner/TestClassInitializationTest.java new file mode 100644 index 0000000000..e9cdba7d16 --- /dev/null +++ b/src/test/java/org/mockitousage/junitrunner/TestClassInitializationTest.java @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2022 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockitousage.junitrunner; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class TestClassInitializationTest { + + private Class CLAZZ = String.class; + + @Test + public void test() {} +}