Repository: mockito/mockito
Issue: Answer with delay in mock or spy to improve testing of asynchronous code
Labels: new feature
noteworthy

I'm currently using Mockito for component testing of microservices.  All code should be run, up to the boundary at which another microservice is called.  In the component test, the mocks return very quickly, but obviously in integration testing, the real calls return much more slowly.  Some threading problems are revealed by the slow returns that would have been revealed in the component test if the returns could be forced to come back more slowly.  Since integration tests are *painfully* slow to start, this would save me, the developer, considerable time in debugging this kind of issue once I discover it in the integration tests.

Because it's so easy to write, I've already solved it with a class called AnswerWithDelay.  It takes another answer as a parameter, and inserts a little sleep before it returns.

If desired, I can turn this little helper into a PR for mockito. Or perhaps there's already something there that does this, that I've overlooked.

Best Regards,
Myrle

```
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT,
        classes = {AbstractRhythmTest.TestConfiguration.class},
        properties = {"rhythm.user=homer", "rhythm.beatCheckRate=500"}
)
public class AbstractRhythmTest {
  //...
  static class AnswerWithDelay<T> implements Answer<T> {
    private final int sleepyTime;
    private final Answer<T> answer;

    AnswerWithDelay(final int sleepyTime, final Answer<T> answer) {
      this.sleepyTime = sleepyTime;
      this.answer = answer;
    }

    @Override
    public T answer(final InvocationOnMock invocation) throws Throwable {
      TimeUnit.MILLISECONDS.sleep(sleepyTime);
      return answer.answer(invocation);
    }
  }

  Beat createBeat(
          final String applicationIdentifier,
          final String beatIdentifier,
          final int alignmentHour,
          final LocalDateTime expectedBeatTimestamp) throws InterruptedException {
    final String tenantIdentifier = tenantDataStoreContext.getTenantName();

    final Beat beat = new Beat();
    beat.setIdentifier(beatIdentifier);
    beat.setAlignmentHour(alignmentHour);

    Mockito.doAnswer(new AnswerWithDelay<>(2_000, new Returns(Optional.of(PermittableGroupIds.forApplication(applicationIdentifier))))).when(beatPublisherServiceSpy).requestPermissionForBeats(Matchers.eq(tenantIdentifier), Matchers.eq(applicationIdentifier));
    Mockito.doAnswer(new AnswerWithDelay<>(2_000, new Returns(true))).when(beatPublisherServiceSpy).publishBeat(Matchers.eq(beatIdentifier), Matchers.eq(tenantIdentifier), Matchers.eq(applicationIdentifier),
            AdditionalMatchers.or(Matchers.eq(expectedBeatTimestamp), Matchers.eq(getNextTimeStamp(expectedBeatTimestamp))));

    this.testSubject.createBeat(applicationIdentifier, beat);

    Assert.assertTrue(this.eventRecorder.wait(EventConstants.POST_BEAT, new BeatEvent(applicationIdentifier, beat.getIdentifier())));

    Mockito.verify(beatPublisherServiceSpy, Mockito.timeout(2_500).times(1)).requestPermissionForBeats(tenantIdentifier, applicationIdentifier);

    return beat;
  }
  //...
}
```

Issue comments:
If the implementation is clean and generic, I would be in favor of it
@szpak For what version is that feature planned (Or has it been already released)?

Pull request review comments:

Patch that resolved the issue:
diff --git a/src/main/java/org/mockito/AdditionalAnswers.java b/src/main/java/org/mockito/AdditionalAnswers.java
index 287e4b1830..214eaa5c47 100644
--- a/src/main/java/org/mockito/AdditionalAnswers.java
+++ b/src/main/java/org/mockito/AdditionalAnswers.java
@@ -5,6 +5,8 @@
 package org.mockito;
 
 import java.util.Collection;
+
+import org.mockito.internal.stubbing.answers.AnswersWithDelay;
 import org.mockito.internal.stubbing.answers.ReturnsArgumentAt;
 import org.mockito.internal.stubbing.answers.ReturnsElementsOf;
 import org.mockito.internal.stubbing.defaultanswers.ForwardsInvocations;
@@ -316,6 +318,21 @@ public static <T> Answer<T> returnsElementsOf(Collection<?> elements) {
         return (Answer<T>) new ReturnsElementsOf(elements);
     }
 
+    /**
+     * Returns an answer after a delay with a defined length.
+     *
+     * @param <T> return type
+     * @param sleepyTime the delay in milliseconds
+     * @param answer interface to the answer which provides the intended return value.
+     * @return the answer object to use
+     *
+     * @since 2.8.44
+     */
+    @Incubating
+    public static <T> Answer<T> answersWithDelay(long sleepyTime, Answer<T> answer) {
+        return (Answer<T>) new AnswersWithDelay(sleepyTime, (Answer<Object>) answer);
+    }
+
     /**
      * Creates an answer from a functional interface - allows for a strongly typed answer to be created
      * ideally in Java 8
diff --git a/src/main/java/org/mockito/internal/stubbing/answers/AnswersWithDelay.java b/src/main/java/org/mockito/internal/stubbing/answers/AnswersWithDelay.java
new file mode 100644
index 0000000000..083b082fcb
--- /dev/null
+++ b/src/main/java/org/mockito/internal/stubbing/answers/AnswersWithDelay.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2017 Mockito contributors
+ * This program is made available under the terms of the MIT License.
+ */
+package org.mockito.internal.stubbing.answers;
+
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.mockito.stubbing.ValidableAnswer;
+
+import java.io.Serializable;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Returns as the provided answer would return, after delaying the specified amount.
+ *
+ * <p>The <code>sleepyTime</code> specifies how long, in milliseconds, to pause before
+ * returning the provided <code>answer</code>.</p>
+ *
+ * @since 2.8.44
+ * @see org.mockito.AdditionalAnswers
+ */
+public class AnswersWithDelay implements Answer<Object>, ValidableAnswer, Serializable {
+    private static final long serialVersionUID = 2177950597971260246L;
+
+    private final long sleepyTime;
+    private final Answer<Object> answer;
+
+    public AnswersWithDelay(final long sleepyTime, final Answer<Object> answer) {
+        this.sleepyTime = sleepyTime;
+        this.answer = answer;
+    }
+
+    @Override
+    public Object answer(final InvocationOnMock invocation) throws Throwable {
+        TimeUnit.MILLISECONDS.sleep(sleepyTime);
+        return answer.answer(invocation);
+    }
+
+    @Override
+    public void validateFor(final InvocationOnMock invocation) {
+        if (answer instanceof ValidableAnswer) {
+            ((ValidableAnswer) answer).validateFor(invocation);
+        }
+    }
+}
+


Test patch:
diff --git a/src/test/java/org/mockito/internal/stubbing/answers/AnswersWithDelayTest.java b/src/test/java/org/mockito/internal/stubbing/answers/AnswersWithDelayTest.java
new file mode 100644
index 0000000000..35c526555b
--- /dev/null
+++ b/src/test/java/org/mockito/internal/stubbing/answers/AnswersWithDelayTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2017 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.mockito.exceptions.base.MockitoException;
+import org.mockito.internal.invocation.InvocationBuilder;
+
+import java.util.Date;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.within;
+
+public class AnswersWithDelayTest {
+    @Test
+    public void should_return_value() throws Throwable {
+        assertThat(new AnswersWithDelay(1, new Returns("value")).answer(new InvocationBuilder().method("oneArg").arg("A").toInvocation())).isEqualTo("value");
+    }
+
+    @Test(expected = MockitoException.class)
+    public void should_fail_when_contained_answer_should_fail() throws Throwable {
+        new AnswersWithDelay(1, new Returns("one")).validateFor(new InvocationBuilder().method("voidMethod").toInvocation());
+    }
+
+    @Test
+    public void should_succeed_when_contained_answer_should_succeed() throws Throwable {
+        new AnswersWithDelay(1, new Returns("one")).validateFor(new InvocationBuilder().simpleMethod().toInvocation());
+    }
+
+    @Test
+    public void should_delay() throws Throwable {
+        final long sleepyTime = 500L;
+
+        final AnswersWithDelay testSubject = new AnswersWithDelay(sleepyTime, new Returns("value"));
+
+        final Date before = new Date();
+        testSubject.answer(new InvocationBuilder().method("oneArg").arg("A").toInvocation());
+        final Date after = new Date();
+
+        final long timePassed = after.getTime() - before.getTime();
+        assertThat(timePassed).isCloseTo(sleepyTime, within(15L));
+    }
+}
+
diff --git a/src/test/java/org/mockitousage/stubbing/StubbingWithAdditionalAnswersTest.java b/src/test/java/org/mockitousage/stubbing/StubbingWithAdditionalAnswersTest.java
index e67f711b77..417d998cc5 100644
--- a/src/test/java/org/mockitousage/stubbing/StubbingWithAdditionalAnswersTest.java
+++ b/src/test/java/org/mockitousage/stubbing/StubbingWithAdditionalAnswersTest.java
@@ -5,17 +5,17 @@
 package org.mockitousage.stubbing;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.within;
 import static org.mockito.AdditionalAnswers.answer;
 import static org.mockito.AdditionalAnswers.answerVoid;
 import static org.mockito.AdditionalAnswers.returnsArgAt;
 import static org.mockito.AdditionalAnswers.returnsFirstArg;
 import static org.mockito.AdditionalAnswers.returnsLastArg;
 import static org.mockito.AdditionalAnswers.returnsSecondArg;
+import static org.mockito.AdditionalAnswers.answersWithDelay;
 import static org.mockito.BDDMockito.any;
 import static org.mockito.BDDMockito.anyInt;
-import static org.mockito.BDDMockito.anyObject;
 import static org.mockito.BDDMockito.anyString;
-import static org.mockito.BDDMockito.anyVararg;
 import static org.mockito.BDDMockito.eq;
 import static org.mockito.BDDMockito.given;
 import static org.mockito.BDDMockito.mock;
@@ -37,6 +37,8 @@
 import org.mockito.stubbing.VoidAnswer5;
 import org.mockitousage.IMethods;
 
+import java.util.Date;
+
 @RunWith(MockitoJUnitRunner.class)
 public class StubbingWithAdditionalAnswersTest {
 
@@ -44,18 +46,32 @@ public class StubbingWithAdditionalAnswersTest {
 
     @Test
     public void can_return_arguments_of_invocation() throws Exception {
-        given(iMethods.objectArgMethod(anyObject())).will(returnsFirstArg());
-        given(iMethods.threeArgumentMethod(eq(0), anyObject(), anyString())).will(returnsSecondArg());
-        given(iMethods.threeArgumentMethod(eq(1), anyObject(), anyString())).will(returnsLastArg());
+        given(iMethods.objectArgMethod(any())).will(returnsFirstArg());
+        given(iMethods.threeArgumentMethod(eq(0), any(), anyString())).will(returnsSecondArg());
+        given(iMethods.threeArgumentMethod(eq(1), any(), anyString())).will(returnsLastArg());
 
         assertThat(iMethods.objectArgMethod("first")).isEqualTo("first");
         assertThat(iMethods.threeArgumentMethod(0, "second", "whatever")).isEqualTo("second");
         assertThat(iMethods.threeArgumentMethod(1, "whatever", "last")).isEqualTo("last");
     }
 
+    @Test
+    public void can_return_after_delay() throws Exception {
+        final long sleepyTime = 500L;
+
+        given(iMethods.objectArgMethod(any())).will(answersWithDelay(sleepyTime, returnsFirstArg()));
+
+        final Date before = new Date();
+        assertThat(iMethods.objectArgMethod("first")).isEqualTo("first");
+        final Date after = new Date();
+
+        final long timePassed = after.getTime() - before.getTime();
+        assertThat(timePassed).isCloseTo(sleepyTime, within(15L));
+    }
+
     @Test
     public void can_return_expanded_arguments_of_invocation() throws Exception {
-        given(iMethods.varargsObject(eq(1), anyVararg())).will(returnsArgAt(3));
+        given(iMethods.varargsObject(eq(1), any())).will(returnsArgAt(3));
 
         assertThat(iMethods.varargsObject(1, "bob", "alexander", "alice", "carl")).isEqualTo("alice");
     }
@@ -163,7 +179,7 @@ public void answer(Integer i, String s1, String s2) {
     }
 
     @Test
-        public void can_return_based_on_strongly_typed_four_parameter_function() throws Exception {
+    public void can_return_based_on_strongly_typed_four_parameter_function() throws Exception {
         final IMethods target = mock(IMethods.class);
         given(iMethods.fourArgumentMethod(anyInt(), anyString(), anyString(), any(boolean[].class)))
                 .will(answer(new Answer4<String, Integer, String, String, boolean[]>() {