From 2874cd24c87b44685bbb38513fb164319c2edc9d Mon Sep 17 00:00:00 2001 From: Gary Russell Date: Mon, 9 Dec 2019 12:23:09 -0500 Subject: [PATCH] Mockito Answers: Capture any exceptions --- .gitignore | 1 + .../rabbit/test/mockito/LambdaAnswer.java | 33 +++++++++++++++--- ...LatchCountDownAndCallRealMethodAnswer.java | 34 ++++++++++++++++--- ...xampleRabbitListenerSpyAndCaptureTest.java | 9 +++-- .../amqp/rabbit/test/mockito/AnswerTests.java | 12 +++++++ src/reference/asciidoc/testing.adoc | 14 ++------ 6 files changed, 80 insertions(+), 23 deletions(-) diff --git a/.gitignore b/.gitignore index 598a88e4..3b3bb754 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ erl_crash.dump nohup.out src/ant/.ant-targets-upload-dist.xml target +.sts4-cache diff --git a/spring-rabbit-test/src/main/java/org/springframework/amqp/rabbit/test/mockito/LambdaAnswer.java b/spring-rabbit-test/src/main/java/org/springframework/amqp/rabbit/test/mockito/LambdaAnswer.java index c6cdfdd1..b7821367 100644 --- a/spring-rabbit-test/src/main/java/org/springframework/amqp/rabbit/test/mockito/LambdaAnswer.java +++ b/spring-rabbit-test/src/main/java/org/springframework/amqp/rabbit/test/mockito/LambdaAnswer.java @@ -16,12 +16,17 @@ package org.springframework.amqp.rabbit.test.mockito; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; + import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; /** - * An Answer to optionally call the real method and allow returning a - * custom result. + * An {@link Answer} to optionally call the real method and allow returning a + * custom result. Captures any exceptions thrown. * * @author Gary Russell * @since 1.6 @@ -33,6 +38,8 @@ public class LambdaAnswer implements Answer { private final ValueToReturn callback; + private final Set exceptions = Collections.synchronizedSet(new LinkedHashSet<>()); + public LambdaAnswer(boolean callRealMethod, ValueToReturn callback) { this.callRealMethod = callRealMethod; this.callback = callback; @@ -42,12 +49,28 @@ public class LambdaAnswer implements Answer { @Override public T answer(InvocationOnMock invocation) throws Throwable { T result = null; - if (this.callRealMethod) { - result = (T) invocation.callRealMethod(); + try { + if (this.callRealMethod) { + result = (T) invocation.callRealMethod(); + } + return this.callback.apply(invocation, result); + } + catch (Exception e) { + this.exceptions.add(e); + throw e; } - return this.callback.apply(invocation, result); } + /** + * Return the exceptions thrown, if any. + * @return the exceptions. + * @since 2.2.3 + */ + public Collection getExceptions() { + return Collections.unmodifiableCollection(this.exceptions); + } + + @FunctionalInterface public interface ValueToReturn { T apply(InvocationOnMock invocation, T result); diff --git a/spring-rabbit-test/src/main/java/org/springframework/amqp/rabbit/test/mockito/LatchCountDownAndCallRealMethodAnswer.java b/spring-rabbit-test/src/main/java/org/springframework/amqp/rabbit/test/mockito/LatchCountDownAndCallRealMethodAnswer.java index 15d4f2ef..4dc985c9 100644 --- a/spring-rabbit-test/src/main/java/org/springframework/amqp/rabbit/test/mockito/LatchCountDownAndCallRealMethodAnswer.java +++ b/spring-rabbit-test/src/main/java/org/springframework/amqp/rabbit/test/mockito/LatchCountDownAndCallRealMethodAnswer.java @@ -16,14 +16,20 @@ package org.springframework.amqp.rabbit.test.mockito; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; import java.util.concurrent.CountDownLatch; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; +import org.springframework.lang.Nullable; + /** - * An Answer for void returning methods that calls the real method and - * counts down a latch. + * An {@link Answer} for void returning methods that calls the real method and counts down + * a latch. Captures any exceptions thrown. * * @author Gary Russell * @since 1.6 @@ -33,6 +39,8 @@ public class LatchCountDownAndCallRealMethodAnswer implements Answer { private final CountDownLatch latch; + private final Set exceptions = Collections.synchronizedSet(new LinkedHashSet<>()); + /** * @param count to set in a {@link CountDownLatch}. */ @@ -42,8 +50,16 @@ public class LatchCountDownAndCallRealMethodAnswer implements Answer { @Override public Void answer(InvocationOnMock invocation) throws Throwable { - invocation.callRealMethod(); - this.latch.countDown(); + try { + invocation.callRealMethod(); + } + catch (Exception e) { + this.exceptions.add(e); + throw e; + } + finally { + this.latch.countDown(); + } return null; } @@ -52,4 +68,14 @@ public class LatchCountDownAndCallRealMethodAnswer implements Answer { return latch; } + /** + * Return the exceptions thrown. + * @return the exceptions. + * @since 2.2.3 + */ + @Nullable + public Collection getExceptions() { + return Collections.unmodifiableCollection(this.exceptions); + } + } diff --git a/spring-rabbit-test/src/test/java/org/springframework/amqp/rabbit/test/ExampleRabbitListenerSpyAndCaptureTest.java b/spring-rabbit-test/src/test/java/org/springframework/amqp/rabbit/test/ExampleRabbitListenerSpyAndCaptureTest.java index 213a11da..3498c74f 100644 --- a/spring-rabbit-test/src/test/java/org/springframework/amqp/rabbit/test/ExampleRabbitListenerSpyAndCaptureTest.java +++ b/spring-rabbit-test/src/test/java/org/springframework/amqp/rabbit/test/ExampleRabbitListenerSpyAndCaptureTest.java @@ -21,6 +21,7 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.verify; +import java.util.Collection; import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; @@ -122,6 +123,10 @@ public class ExampleRabbitListenerSpyAndCaptureTest { assertThat((String) args[0]).isEqualTo("ex"); assertThat((String) args[1]).isEqualTo(queue2.getName()); assertThat(invocationData.getThrowable()).isNull(); + + Collection exceptions = answer.getExceptions(); + assertThat(exceptions).hasSize(1); + assertThat(exceptions.iterator().next()).isInstanceOf(IllegalArgumentException.class); } @Configuration @@ -177,10 +182,10 @@ public class ExampleRabbitListenerSpyAndCaptureTest { } @RabbitListener(id = "bar", queues = "#{queue2.name}") - public void foo(@Payload String foo, @Header("amqp_receivedRoutingKey") String rk) { + public void foo(@Payload String foo, @SuppressWarnings("unused") @Header("amqp_receivedRoutingKey") String rk) { if (!failed && foo.equals("ex")) { failed = true; - throw new RuntimeException(foo); + throw new IllegalArgumentException(foo); } failed = false; } diff --git a/spring-rabbit-test/src/test/java/org/springframework/amqp/rabbit/test/mockito/AnswerTests.java b/spring-rabbit-test/src/test/java/org/springframework/amqp/rabbit/test/mockito/AnswerTests.java index 70b5abac..f23c9aaa 100644 --- a/spring-rabbit-test/src/test/java/org/springframework/amqp/rabbit/test/mockito/AnswerTests.java +++ b/spring-rabbit-test/src/test/java/org/springframework/amqp/rabbit/test/mockito/AnswerTests.java @@ -17,10 +17,13 @@ package org.springframework.amqp.rabbit.test.mockito; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.BDDMockito.willAnswer; import static org.mockito.Mockito.spy; +import java.util.Collection; + import org.junit.jupiter.api.Test; /** @@ -40,6 +43,12 @@ public class AnswerTests { willAnswer(new LambdaAnswer(false, (i, r) -> "" + i.getArguments()[0] + i.getArguments()[0])).given(foo).foo(anyString()); assertThat(foo.foo("foo")).isEqualTo("foofoo"); + LambdaAnswer answer = new LambdaAnswer<>(true, (inv, result) -> result); + willAnswer(answer).given(foo).foo("fail"); + assertThatIllegalArgumentException().isThrownBy(() -> foo.foo("fail")); + Collection exceptions = answer.getExceptions(); + assertThat(exceptions).hasSize(1); + assertThat(exceptions.iterator().next()).isInstanceOf(IllegalArgumentException.class); } private static class Foo { @@ -49,6 +58,9 @@ public class AnswerTests { } public String foo(String foo) { + if (foo.equals("fail")) { + throw new IllegalArgumentException("fail"); + } return foo.toUpperCase(); } diff --git a/src/reference/asciidoc/testing.adoc b/src/reference/asciidoc/testing.adoc index db178cc1..fff27576 100644 --- a/src/reference/asciidoc/testing.adoc +++ b/src/reference/asciidoc/testing.adoc @@ -73,19 +73,9 @@ assertEquals("thingthing", thing.thing("thing")); ---- ==== -The following example shows how to test the `Thing` POJO with Java 7 or earlier: +Starting with version 2.2.3, the answers capture any exceptions thrown by the method under test. +Use `answer.getExceptions()` to get a reference to them. -==== -[source, java] ----- -doAnswer(new LambdaAnswer(true, new ValueToReturn() { - @Override - public String apply(InvocationOnMock i, String r) { - return r + r; - } -})).when(thing).thing(anyString()); ----- -==== [[test-harness]] ==== `@RabbitListenerTest` and `RabbitListenerTestHarness`