From 264e2fae3f1552fdde930c0cade926657a35527a Mon Sep 17 00:00:00 2001 From: Andrey Kozel Date: Sun, 23 Jan 2022 21:04:04 +0300 Subject: [PATCH] Fixes #2201 : Fixed checking of declared exceptions. In case when parent contains throws keyword on its method and child overrides this method removing throws, it should be possible to mock throwing exception from child --- .../stubbing/answers/InvocationInfo.java | 23 ++++++++++++++++++- .../invocation/InvocationBuilder.java | 9 +++++++- .../stubbing/answers/InvocationInfoTest.java | 15 ++++++++++++ src/test/java/org/mockitousage/IMethods.java | 2 ++ .../java/org/mockitousage/MethodsImpl.java | 5 ++++ 5 files changed, 52 insertions(+), 2 deletions(-) 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; }