Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #2201 : Fixed checking of declared exceptions. #2547

Merged
merged 2 commits into from Jan 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -6,6 +6,9 @@

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.mockito.internal.invocation.AbstractAwareMethod;
import org.mockito.internal.util.MockUtil;
Expand All @@ -24,15 +27,52 @@ public InvocationInfo(InvocationOnMock theInvocation) {
this.invocation = theInvocation;
}

public boolean isValidException(Throwable throwable) {
Class<?>[] exceptions = method.getExceptionTypes();
Class<?> throwableClass = throwable.getClass();
for (Class<?> exception : exceptions) {
public boolean isValidException(final Throwable throwable) {
if (isValidException(method, throwable)) {
return true;
}

return isValidExceptionForParents(method.getDeclaringClass(), throwable);
}

private boolean isValidExceptionForParents(final Class<?> parent, final Throwable throwable) {
final List<Class<?>> ancestors = new ArrayList<>(Arrays.asList(parent.getInterfaces()));

if (parent.getSuperclass() != null) {
ancestors.add(parent.getSuperclass());
}

final boolean validException =
ancestors.stream()
.anyMatch(ancestor -> isValidExceptionForClass(ancestor, throwable));

if (validException) {
return true;
}

return ancestors.stream()
.anyMatch(ancestor -> isValidExceptionForParents(ancestor, throwable));
}

private boolean isValidExceptionForClass(final Class<?> parent, final Throwable throwable) {
try {
final Method parentMethod =
parent.getMethod(this.method.getName(), this.method.getParameterTypes());
return isValidException(parentMethod, throwable);
} catch (NoSuchMethodException e) {
// ignore interfaces that doesn't have such a method
return false;
}
}

private boolean isValidException(final Method method, final Throwable throwable) {
final Class<?>[] exceptions = method.getExceptionTypes();
final Class<?> throwableClass = throwable.getClass();
for (final Class<?> exception : exceptions) {
if (exception.isAssignableFrom(throwableClass)) {
return true;
}
}

return false;
}

Expand Down
Expand Up @@ -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<Class<?>> argTypes;
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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;
Expand Down
@@ -0,0 +1,95 @@
/*
* Copyright (c) 2022 Mockito contributors
* This program is made available under the terms of the MIT License.
*/
package org.mockito.internal.stubbing.answers;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.mockito.internal.invocation.InvocationBuilder;
import org.mockito.invocation.Invocation;

import java.nio.charset.CharacterCodingException;
import java.util.Arrays;
import java.util.Collection;

import static org.assertj.core.api.Assertions.assertThat;

@RunWith(Parameterized.class)
public class InvocationInfoExceptionTest {

private final String methodName;

public InvocationInfoExceptionTest(final String methodName) {
this.methodName = methodName;
}

@Parameterized.Parameters
public static Collection<Object[]> data() {
return Arrays.asList(
new Object[][] {
{"throwException"},
{"parentThrowsException"},
{"grandParentThrowsException"},
{"interfaceThrowsException"},
{"grandInterfaceThrowsException"},
{"interfaceOfParentThrowsException"}
});
}

@Test
public void should_know_valid_throwables() throws Exception {
// when
final Invocation invocation =
new InvocationBuilder()
.method(methodName)
.mockClass(CurrentClass.class)
.toInvocation();
final InvocationInfo info = new InvocationInfo(invocation);

// then
assertThat(info.isValidException(new Exception())).isFalse();
assertThat(info.isValidException(new CharacterCodingException())).isTrue();
}

private abstract static class GrandParent {
public abstract void grandParentThrowsException() throws CharacterCodingException;
}

private interface InterfaceOfParent {
abstract void interfaceOfParentThrowsException() throws CharacterCodingException;
}

private abstract static class Parent extends GrandParent implements InterfaceOfParent {
public abstract void parentThrowsException() throws CharacterCodingException;
}

private interface GrandInterface {
void grandInterfaceThrowsException() throws CharacterCodingException;
}

private interface Interface extends GrandInterface {
void interfaceThrowsException() throws CharacterCodingException;
}

private static class CurrentClass extends Parent implements Interface {

public void throwException() throws CharacterCodingException {}

@Override
public void grandParentThrowsException() {}

@Override
public void parentThrowsException() {}

@Override
public void grandInterfaceThrowsException() {}

@Override
public void interfaceThrowsException() {}

@Override
public void interfaceOfParentThrowsException() {}
}
}
Expand Up @@ -9,26 +9,13 @@
import static org.mockitoutil.TestBase.getLastInvocation;

import java.lang.reflect.Method;
import java.nio.charset.CharacterCodingException;

import org.junit.Test;
import org.mockito.internal.invocation.InvocationBuilder;
import org.mockito.invocation.Invocation;
import org.mockitousage.IMethods;

public class InvocationInfoTest {

@Test
public void should_know_valid_throwables() throws Exception {
// when
Invocation invocation = new InvocationBuilder().method("canThrowException").toInvocation();
InvocationInfo info = new InvocationInfo(invocation);

// then
assertThat(info.isValidException(new Exception())).isFalse();
assertThat(info.isValidException(new CharacterCodingException())).isTrue();
}

@Test
public void should_know_valid_return_types() throws Exception {
assertThat(
Expand Down