Repository: mockito/mockito
Issue: thenThrow(Class) should only throw checked exceptions that match the signature of the stubbed method

Hi,

- mockito-core 2.8.47

```java
public class FooTest {

    @Test(expected = MockitoException.class)
    public void using_thenThrow_Throwable() {
        C mock = mock(C.class);
        // Set a checked exception that does not matche the method signature
        when(mock.doIt()).thenThrow(new Exception());
    }

    @Test(expected = MockitoException.class)
    public void using_thenThrow_Class() {
        C mock = mock(C.class);
        // Set a checked exception that does not matche the method signature
        when(mock.doIt()).thenThrow(Exception.class);
    }

    static class C {
        String doIt() {
            return null;
        }
    }

}
```

`using_thenThrow_Throwable()` passes, but `using_thenThrow_Class()` fails.

```
java.lang.AssertionError: Expected exception: org.mockito.exceptions.base.MockitoException
```

The Javadoc of `OngoingStubbing#thenThrow(Class)` says, *If the throwable class is a checked exception then it has to match one of the checked exceptions of the stubbed method signature*.
However, the result of `using_thenThrow_Class()` shows that it is not so.


Issue comments:
I am going to fix this. 

ThrowsExceptionClass must simply implement ValidableAnswer...




Thank you for reporting. Indeed it is a bug.

Pull request review comments:
Missing the ewuals here
Missing equals here
Missing equals here
Can you give me some more infos? We expect the instance to be thrown that was passed to the stubbing, right?
Lets better use: `exception.expect(sameInstance(expected));`
Sorry, this one is correct. I misread

Patch that resolved the issue:
diff --git a/src/main/java/org/mockito/internal/stubbing/BaseStubbing.java b/src/main/java/org/mockito/internal/stubbing/BaseStubbing.java
index c55c82b99a..ddb9a8dcb9 100644
--- a/src/main/java/org/mockito/internal/stubbing/BaseStubbing.java
+++ b/src/main/java/org/mockito/internal/stubbing/BaseStubbing.java
@@ -4,26 +4,30 @@
  */
 package org.mockito.internal.stubbing;
 
+import static org.mockito.internal.exceptions.Reporter.notAnException;
+import static org.mockito.internal.progress.ThreadSafeMockingProgress.mockingProgress;
+import static org.objenesis.ObjenesisHelper.newInstance;
+
 import org.mockito.internal.stubbing.answers.CallsRealMethods;
 import org.mockito.internal.stubbing.answers.Returns;
 import org.mockito.internal.stubbing.answers.ThrowsException;
-import org.mockito.internal.stubbing.answers.ThrowsExceptionClass;
 import org.mockito.stubbing.OngoingStubbing;
 
 public abstract class BaseStubbing<T> implements OngoingStubbing<T> {
 
+    @Override
     public OngoingStubbing<T> thenReturn(T value) {
         return thenAnswer(new Returns(value));
     }
 
-    @SuppressWarnings({"unchecked","vararg"})
+    @Override
     public OngoingStubbing<T> thenReturn(T value, T... values) {
         OngoingStubbing<T> stubbing = thenReturn(value);
         if (values == null) {
-            //TODO below does not seem right
+            // TODO below does not seem right
             return stubbing.thenReturn(null);
         }
-        for (T v: values) {
+        for (T v : values) {
             stubbing = stubbing.thenReturn(v);
         }
         return stubbing;
@@ -33,12 +37,13 @@ private OngoingStubbing<T> thenThrow(Throwable throwable) {
         return thenAnswer(new ThrowsException(throwable));
     }
 
+    @Override
     public OngoingStubbing<T> thenThrow(Throwable... throwables) {
         if (throwables == null) {
             return thenThrow((Throwable) null);
         }
         OngoingStubbing<T> stubbing = null;
-        for (Throwable t: throwables) {
+        for (Throwable t : throwables) {
             if (stubbing == null) {
                 stubbing = thenThrow(t);
             } else {
@@ -48,23 +53,31 @@ public OngoingStubbing<T> thenThrow(Throwable... throwables) {
         return stubbing;
     }
 
+    @Override
     public OngoingStubbing<T> thenThrow(Class<? extends Throwable> throwableType) {
-        return thenAnswer(new ThrowsExceptionClass(throwableType));
+        if (throwableType == null) {
+            mockingProgress().reset();
+            throw notAnException();
+        }
+        return thenThrow(newInstance(throwableType));
     }
 
-    @SuppressWarnings ({"unchecked", "varargs"})
+    @Override
     public OngoingStubbing<T> thenThrow(Class<? extends Throwable> toBeThrown, Class<? extends Throwable>... nextToBeThrown) {
         if (nextToBeThrown == null) {
-            thenThrow((Throwable) null);
+            thenThrow((Class<Throwable>) null);
         }
         OngoingStubbing<T> stubbing = thenThrow(toBeThrown);
-        for (Class<? extends Throwable> t: nextToBeThrown) {
+        for (Class<? extends Throwable> t : nextToBeThrown) {
             stubbing = stubbing.thenThrow(t);
         }
         return stubbing;
     }
 
+    @Override
     public OngoingStubbing<T> thenCallRealMethod() {
         return thenAnswer(new CallsRealMethods());
     }
 }
+
+
diff --git a/src/main/java/org/mockito/internal/stubbing/OngoingStubbingImpl.java b/src/main/java/org/mockito/internal/stubbing/OngoingStubbingImpl.java
index fd47434a75..78a10270a3 100644
--- a/src/main/java/org/mockito/internal/stubbing/OngoingStubbingImpl.java
+++ b/src/main/java/org/mockito/internal/stubbing/OngoingStubbingImpl.java
@@ -20,6 +20,7 @@ public OngoingStubbingImpl(InvocationContainerImpl invocationContainer) {
         this.invocationContainer = invocationContainer;
     }
 
+    @Override
     public OngoingStubbing<T> thenAnswer(Answer<?> answer) {
         if(!invocationContainer.hasInvocationForPotentialStubbing()) {
             throw incorrectUseOfApi();
@@ -29,6 +30,7 @@ public OngoingStubbing<T> thenAnswer(Answer<?> answer) {
         return new ConsecutiveStubbing<T>(invocationContainer);
     }
 
+    @Override
     public OngoingStubbing<T> then(Answer<?> answer) {
         return thenAnswer(answer);
     }
@@ -38,8 +40,11 @@ public List<Invocation> getRegisteredInvocations() {
         return invocationContainer.getInvocations();
     }
 
+    @Override
     @SuppressWarnings("unchecked")
     public <M> M getMock() {
         return (M) invocationContainer.invokedMock();
     }
 }
+
+
diff --git a/src/main/java/org/mockito/internal/stubbing/StubberImpl.java b/src/main/java/org/mockito/internal/stubbing/StubberImpl.java
index 89255ab791..efe74880ca 100644
--- a/src/main/java/org/mockito/internal/stubbing/StubberImpl.java
+++ b/src/main/java/org/mockito/internal/stubbing/StubberImpl.java
@@ -5,8 +5,12 @@
 package org.mockito.internal.stubbing;
 
 import static org.mockito.internal.exceptions.Reporter.notAMockPassedToWhenMethod;
+import static org.mockito.internal.exceptions.Reporter.notAnException;
 import static org.mockito.internal.exceptions.Reporter.nullPassedToWhenMethod;
+import static org.mockito.internal.progress.ThreadSafeMockingProgress.mockingProgress;
 import static org.mockito.internal.stubbing.answers.DoesNothing.doesNothing;
+import static org.mockito.internal.util.MockUtil.isMock;
+import static org.objenesis.ObjenesisHelper.newInstance;
 
 import java.util.LinkedList;
 import java.util.List;
@@ -14,12 +18,10 @@
 import org.mockito.internal.stubbing.answers.CallsRealMethods;
 import org.mockito.internal.stubbing.answers.Returns;
 import org.mockito.internal.stubbing.answers.ThrowsException;
-import org.mockito.internal.stubbing.answers.ThrowsExceptionClass;
 import org.mockito.internal.util.MockUtil;
 import org.mockito.stubbing.Answer;
 import org.mockito.stubbing.Stubber;
 
-@SuppressWarnings("unchecked")
 public class StubberImpl implements Stubber {
 
     private final List<Answer<?>> answers = new LinkedList<Answer<?>>();
@@ -30,11 +32,13 @@ public <T> T when(T mock) {
             throw nullPassedToWhenMethod();
         }
 
-		if (!MockUtil.isMock(mock)) {
-			throw notAMockPassedToWhenMethod();
-		}
+        if (!isMock(mock)) {
+            throw notAMockPassedToWhenMethod();
+        }
+
+
+		    MockUtil.getInvocationContainer(mock).setAnswersForStubbing(answers);
 
-		MockUtil.getInvocationContainer(mock).setAnswersForStubbing(answers);
         return mock;
     }
 
@@ -49,7 +53,7 @@ public Stubber doReturn(Object toBeReturned, Object... nextToBeReturned) {
     }
 
     private StubberImpl doReturnValues(Object... toBeReturned) {
-        if(toBeReturned == null) {
+        if (toBeReturned == null) {
             answers.add(new Returns(null));
             return this;
         }
@@ -61,7 +65,7 @@ private StubberImpl doReturnValues(Object... toBeReturned) {
 
     @Override
     public Stubber doThrow(Throwable... toBeThrown) {
-        if(toBeThrown == null) {
+        if (toBeThrown == null) {
             answers.add(new ThrowsException(null));
             return this;
         }
@@ -73,19 +77,34 @@ public Stubber doThrow(Throwable... toBeThrown) {
 
     @Override
     public Stubber doThrow(Class<? extends Throwable> toBeThrown) {
-        return doThrowClasses(toBeThrown);
+        if (toBeThrown == null) {
+            mockingProgress().reset();
+            throw notAnException();
+        }
+        Throwable e;
+        try {
+            e = newInstance(toBeThrown);
+        } catch (RuntimeException instanciationError) {
+            mockingProgress().reset();
+            throw instanciationError;
+        }
+        return doThrow(e);
     }
 
     @Override
     public Stubber doThrow(Class<? extends Throwable> toBeThrown, Class<? extends Throwable>... nextToBeThrown) {
-        return doThrowClasses(toBeThrown).doThrowClasses(nextToBeThrown);
-    }
+        Stubber stubber = doThrow(toBeThrown);
 
-    private StubberImpl doThrowClasses(Class<? extends Throwable>... toBeThrown) {
-        for (Class<? extends Throwable> throwable: toBeThrown) {
-            answers.add(new ThrowsExceptionClass(throwable));
+        if (nextToBeThrown == null) {
+            mockingProgress().reset();
+            throw notAnException();
         }
-        return this;
+
+        for (Class<? extends Throwable> next : nextToBeThrown) {
+            stubber = stubber.doThrow(next);
+        }
+        return stubber;
+
     }
 
     @Override
@@ -106,3 +125,5 @@ public Stubber doCallRealMethod() {
         return this;
     }
 }
+
+
diff --git a/src/main/java/org/mockito/internal/stubbing/answers/ThrowsExceptionClass.java b/src/main/java/org/mockito/internal/stubbing/answers/ThrowsExceptionClass.java
deleted file mode 100644
index ef7baad764..0000000000
--- a/src/main/java/org/mockito/internal/stubbing/answers/ThrowsExceptionClass.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (c) 2007 Mockito contributors
- * This program is made available under the terms of the MIT License.
- */
-
-package org.mockito.internal.stubbing.answers;
-
-import org.mockito.internal.exceptions.stacktrace.ConditionalStackTraceFilter;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-import org.objenesis.ObjenesisHelper;
-
-import static org.mockito.internal.exceptions.Reporter.notAnException;
-
-import java.io.Serializable;
-
-public class ThrowsExceptionClass implements Answer<Object>, Serializable {
-
-    private final Class<? extends Throwable> throwableClass;
-    private final ConditionalStackTraceFilter filter = new ConditionalStackTraceFilter();
-
-    public ThrowsExceptionClass(Class<? extends Throwable> throwableClass) {
-        this.throwableClass = checkNonNullThrowable(throwableClass);
-    }
-
-    private Class<? extends Throwable> checkNonNullThrowable(Class<? extends Throwable> throwableClass) {
-        if(throwableClass == null || !Throwable.class.isAssignableFrom(throwableClass)) {
-            throw notAnException();
-        }
-        return throwableClass;
-    }
-
-    public Object answer(InvocationOnMock invocation) throws Throwable {
-        //TODO centralize the use of Objenesis. Why do we use ObjenesisHelper?
-        Throwable throwable = ObjenesisHelper.newInstance(throwableClass);
-        throwable.fillInStackTrace();
-        filter.filter(throwable);
-        throw throwable;
-    }
-
-    public Class<? extends Throwable> getThrowableClass() {
-        return throwableClass;
-    }
-}


Test patch:
diff --git a/src/test/java/org/mockitousage/stubbing/StubbingWithThrowablesTest.java b/src/test/java/org/mockitousage/stubbing/StubbingWithThrowablesTest.java
index 0ccf14f268..1931bd1de2 100644
--- a/src/test/java/org/mockitousage/stubbing/StubbingWithThrowablesTest.java
+++ b/src/test/java/org/mockitousage/stubbing/StubbingWithThrowablesTest.java
@@ -5,29 +5,43 @@
 
 package org.mockitousage.stubbing;
 
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.exceptions.base.MockitoException;
-import org.mockito.exceptions.verification.NoInteractionsWanted;
-import org.mockito.exceptions.verification.WantedButNotInvoked;
-import org.mockitoutil.TestBase;
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertNull;
+import static junit.framework.TestCase.assertTrue;
+import static junit.framework.TestCase.fail;
+import static org.hamcrest.CoreMatchers.sameInstance;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
 
 import java.io.IOException;
 import java.io.Reader;
 import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.Map;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.mockito.exceptions.base.MockitoException;
+import org.mockito.exceptions.verification.NoInteractionsWanted;
+import org.mockito.exceptions.verification.WantedButNotInvoked;
+import org.mockitousage.IMethods;
+import org.mockitoutil.TestBase;
 
-import static junit.framework.TestCase.*;
-import static org.mockito.Mockito.*;
-
-@SuppressWarnings({"serial", "unchecked", "all", "deprecation"})
+@SuppressWarnings({ "serial", "unchecked", "rawtypes" })
 public class StubbingWithThrowablesTest extends TestBase {
 
     private LinkedList mock;
 
     private Map mockTwo;
 
+    @Rule
+    public ExpectedException exception = ExpectedException.none();
+
     @Before
     public void setup() {
         mock = mock(LinkedList.class);
@@ -39,12 +53,9 @@ public void shouldStubWithThrowable() throws Exception {
         IllegalArgumentException expected = new IllegalArgumentException("thrown by mock");
         when(mock.add("throw")).thenThrow(expected);
 
-        try {
-            mock.add("throw");
-            fail();
-        } catch (IllegalArgumentException e) {
-            assertEquals(expected, e);
-        }
+        exception.expect(sameInstance(expected));
+        mock.add("throw");
+
     }
 
     @Test
@@ -52,12 +63,11 @@ public void shouldSetThrowableToVoidMethod() throws Exception {
         IllegalArgumentException expected = new IllegalArgumentException("thrown by mock");
 
         doThrow(expected).when(mock).clear();
-        try {
-            mock.clear();
-            fail();
-        } catch (Exception e) {
-            assertEquals(expected, e);
-        }
+
+        exception.expect(sameInstance(expected));
+
+        mock.clear();
+
     }
 
     @Test
@@ -65,22 +75,18 @@ public void shouldLastStubbingVoidBeImportant() throws Exception {
         doThrow(new ExceptionOne()).when(mock).clear();
         doThrow(new ExceptionTwo()).when(mock).clear();
 
-        try {
-            mock.clear();
-            fail();
-        } catch (ExceptionTwo e) {
-        }
+        exception.expect(ExceptionTwo.class);
+
+        mock.clear();
     }
 
     @Test
     public void shouldFailStubbingThrowableOnTheSameInvocationDueToAcceptableLimitation() throws Exception {
-        when(mock.get(1)).thenThrow(new ExceptionOne());
+        when(mock.size()).thenThrow(new ExceptionOne());
 
-        try {
-            when(mock.get(1)).thenThrow(new ExceptionTwo());
-            fail();
-        } catch (ExceptionOne e) {
-        }
+        exception.expect(ExceptionOne.class);
+
+        when(mock.size()).thenThrow(new ExceptionTwo());
     }
 
     @Test
@@ -90,12 +96,9 @@ public void shouldAllowSettingCheckedException() throws Exception {
 
         when(reader.read()).thenThrow(ioException);
 
-        try {
-            reader.read();
-            fail();
-        } catch (Exception e) {
-            assertEquals(ioException, e);
-        }
+        exception.expect(sameInstance(ioException));
+
+        reader.read();
     }
 
     @Test
@@ -104,49 +107,147 @@ public void shouldAllowSettingError() throws Exception {
 
         when(mock.add("quake")).thenThrow(error);
 
-        try {
-            mock.add("quake");
-            fail();
-        } catch (Error e) {
-            assertEquals(error, e);
-        }
+        exception.expect(Error.class);
+
+        mock.add("quake");
     }
 
-    @Test(expected = MockitoException.class)
+    @Test
     public void shouldNotAllowNullExceptionType() {
+        exception.expect(MockitoException.class);
+        exception.expectMessage("Cannot stub with null throwable");
+
         when(mock.add(null)).thenThrow((Exception) null);
     }
 
-
-    @Test(expected = NaughtyException.class)
+    @Test
     public void shouldInstantiateExceptionClassOnInteraction() {
         when(mock.add(null)).thenThrow(NaughtyException.class);
 
+        exception.expect(NaughtyException.class);
+
         mock.add(null);
     }
 
-    @Test(expected = NaughtyException.class)
+    @Test
     public void shouldInstantiateExceptionClassWithOngoingStubbingOnInteraction() {
         doThrow(NaughtyException.class).when(mock).add(null);
 
+        exception.expect(NaughtyException.class);
+
         mock.add(null);
     }
 
-    @Test(expected = MockitoException.class)
-    public void shouldNotAllowSettingInvalidCheckedException() throws Exception {
+    @Test
+    public void shouldNotAllowSettingInvalidCheckedException() {
+        exception.expect(MockitoException.class);
+        exception.expectMessage("Checked exception is invalid for this method");
+
         when(mock.add("monkey island")).thenThrow(new Exception());
     }
 
-    @Test(expected = MockitoException.class)
-    public void shouldNotAllowSettingNullThrowable() throws Exception {
+    @Test
+    public void shouldNotAllowSettingNullThrowable() {
+        exception.expect(MockitoException.class);
+        exception.expectMessage("Cannot stub with null throwable");
+
         when(mock.add("monkey island")).thenThrow((Throwable) null);
     }
 
-    @Test(expected = MockitoException.class)
-    public void shouldNotAllowSettingNullThrowableArray() throws Exception {
+    @Test
+    public void shouldNotAllowSettingNullThrowableArray() {
+        exception.expect(MockitoException.class);
+        exception.expectMessage("Cannot stub with null throwable");
+
         when(mock.add("monkey island")).thenThrow((Throwable[]) null);
     }
 
+    @Test
+    public void shouldNotAllowSettingNullThrowableClass() {
+        exception.expect(MockitoException.class);
+        exception.expectMessage("Exception type cannot be null");
+
+        when(mock.isEmpty()).thenThrow((Class) null);
+    }
+
+    @Test
+    public void shouldNotAllowSettingNullThrowableClasses() {
+        exception.expect(MockitoException.class);
+        exception.expectMessage("Exception type cannot be null");
+
+        when(mock.isEmpty()).thenThrow(RuntimeException.class, (Class[]) null);
+    }
+
+    @Test
+    public void shouldNotAllowSettingNullVarArgThrowableClass() {
+        exception.expect(MockitoException.class);
+        exception.expectMessage("Exception type cannot be null");
+
+        when(mock.isEmpty()).thenThrow(RuntimeException.class, (Class) null);
+    }
+
+    @Test
+    public void doThrowShouldNotAllowSettingNullThrowableClass() {
+        exception.expect(MockitoException.class);
+        exception.expectMessage("Exception type cannot be null");
+
+        doThrow((Class) null).when(mock).isEmpty();
+    }
+
+    @Test
+    public void doThrowShouldNotAllowSettingNullThrowableClasses() throws Exception {
+        exception.expect(MockitoException.class);
+        exception.expectMessage("Exception type cannot be null");
+
+        doThrow(RuntimeException.class, (Class) null).when(mock).isEmpty();
+    }
+
+    @Test
+    public void doThrowShouldNotAllowSettingNullVarArgThrowableClasses() throws Exception {
+        exception.expect(MockitoException.class);
+        exception.expectMessage("Exception type cannot be null");
+
+        doThrow(RuntimeException.class, (Class[]) null).when(mock).isEmpty();
+    }
+
+    @Test
+    public void shouldNotAllowSettingNullVarArgsThrowableClasses() throws Exception {
+        exception.expect(MockitoException.class);
+        exception.expectMessage("Exception type cannot be null");
+
+        when(mock.isEmpty()).thenThrow(RuntimeException.class, (Class<RuntimeException>[]) null);
+    }
+
+    @Test
+    public void shouldNotAllowDifferntCheckedException() throws Exception {
+        IMethods mock = mock(IMethods.class);
+
+        exception.expect(MockitoException.class);
+        exception.expectMessage("Checked exception is invalid for this method");
+
+        when(mock.throwsIOException(0)).thenThrow(CheckedException.class);
+    }
+
+    @Test
+    public void shouldNotAllowCheckedExceptionWhenErrorIsDeclared() throws Exception {
+        IMethods mock = mock(IMethods.class);
+
+        exception.expect(MockitoException.class);
+        exception.expectMessage("Checked exception is invalid for this method");
+
+        when(mock.throwsError(0)).thenThrow(CheckedException.class);
+    }
+
+    @Test
+    public void shouldNotAllowCheckedExceptionWhenNothingIsDeclared() throws Exception {
+        IMethods mock = mock(IMethods.class);
+
+        exception.expect(MockitoException.class);
+        exception.expectMessage("Checked exception is invalid for this method");
+
+        when(mock.throwsNothing(true)).thenThrow(CheckedException.class);
+    }
+
     @Test
     public void shouldMixThrowablesAndReturnsOnDifferentMocks() throws Exception {
         when(mock.add("ExceptionOne")).thenThrow(new ExceptionOne());
@@ -236,31 +337,23 @@ public void shouldStubbingWithThrowableFailVerification() {
     }
 
     private class ExceptionOne extends RuntimeException {
-
     }
 
     private class ExceptionTwo extends RuntimeException {
-
     }
 
     private class ExceptionThree extends RuntimeException {
-
     }
 
     private class ExceptionFour extends RuntimeException {
+    }
 
+    private class CheckedException extends Exception {
     }
 
     public class NaughtyException extends RuntimeException {
-
         public NaughtyException() {
             throw new RuntimeException("boo!");
         }
     }
-
-    @Test(expected = NaughtyException.class)
-    public void shouldShowDecentMessageWhenExcepionIsNaughty() throws Exception {
-        when(mock.add("")).thenThrow(NaughtyException.class);
-        mock.add("");
-    }
 }