diff --git a/jadler-all/src/test/java/net/jadler/JadlerMockingAsyncIntegrationTest.java b/jadler-all/src/test/java/net/jadler/JadlerMockingAsyncIntegrationTest.java new file mode 100644 index 0000000..b08712c --- /dev/null +++ b/jadler-all/src/test/java/net/jadler/JadlerMockingAsyncIntegrationTest.java @@ -0,0 +1,132 @@ +package net.jadler; + +import net.jadler.mocking.VerificationException; +import net.jadler.mocking.Verifying; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.methods.PostMethod; +import org.apache.commons.httpclient.methods.StringRequestEntity; +import org.apache.commons.lang.time.StopWatch; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.junit.Assert.assertThat; +import static org.hamcrest.Matchers.lessThan; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; + +import java.io.IOException; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + + +import static net.jadler.Jadler.initJadler; +import static net.jadler.Jadler.closeJadler; +import static net.jadler.Jadler.port; +import static net.jadler.Jadler.verifyThatRequest; +import static net.jadler.Jadler.onRequest; + + + +public class JadlerMockingAsyncIntegrationTest { + + private StopWatch stopWatch; + private HttpClient client; + private ScheduledExecutorService exec; + + @Before + public void init() { + initJadler(); + this.stopWatch = new StopWatch(); + this.client = new HttpClient(); + this.exec = Executors.newSingleThreadScheduledExecutor(); + } + + @After + public void shutdown() { + closeJadler(); + this.exec.shutdownNow(); + } + + @Test + public void callHasAlredyBeenMade() throws IOException { + stubOkOnExpectedRequest(); + performExpectedRequest(); + stopWatch.start(); + verifyingObjForExpected() + .receivedOnce(Duration.ofSeconds(10)); + stopWatch.stop(); + assertThat(stopWatch.getTime(), lessThan(1000L)); + // 1 seconds is just to make sure that the test does not fail, because it runs on + // very busy computer, it should actually just return without waiting at all. + } + + @Test + public void callIsMadeAfterOneSecond() throws ExecutionException, InterruptedException { + stubOkOnExpectedRequest(); + stopWatch.start(); + final ScheduledFuture performResult = this.exec.schedule(new Callable() { + @Override + public Integer call() throws Exception { + return performExpectedRequest(); + } + }, 1, TimeUnit.SECONDS); + verifyingObjForExpected().receivedOnce(Duration.ofSeconds(2)); + stopWatch.stop(); + assertEquals(200, performResult.get().intValue()); + assertThat(stopWatch.getTime(), lessThan(2000L)); + assertThat(stopWatch.getTime(), greaterThanOrEqualTo(1000L)); + } + + @Test + public void callIsNeverMadeShouldReturnTheExactSameExceptionAs() { + VerificationException expectedExeption = null; + try { + verifyingObjForExpected().receivedOnce(); + fail("Should have catched an exception by now"); + } catch (VerificationException e) { + expectedExeption = e; + } + Jadler.resetJadler(); + stopWatch.start(); + VerificationException result = null; + try { + verifyingObjForExpected().receivedOnce(Duration.ofMillis(200)); + fail("Should have catched an exception by now"); + } catch (VerificationException e) { + result = e; + } + stopWatch.stop(); + assertThat(stopWatch.getTime(), greaterThanOrEqualTo(200L)); + assertEquals(expectedExeption.getMessage(), result.getMessage()); + } + + private void stubOkOnExpectedRequest() { + addExpectedRequestToRequestMatching(onRequest()) + .respond() + .withStatus(200); + } + + private static Verifying verifyingObjForExpected() { + return addExpectedRequestToRequestMatching(verifyThatRequest()); + } + + private static > T addExpectedRequestToRequestMatching( + final RequestMatching r) { + return r + .havingMethodEqualTo("POST") + .havingPathEqualTo("/expectedPath") + .havingBodyEqualTo("expected body"); + } + + private int performExpectedRequest() throws IOException { + final PostMethod method = new PostMethod("http://localhost:"+port()+"/expectedPath"); + method.setRequestEntity(new StringRequestEntity("expected body", "text/plain", "UTF-8")); + return client.executeMethod(method); + } +} diff --git a/jadler-core/src/main/java/net/jadler/Duration.java b/jadler-core/src/main/java/net/jadler/Duration.java new file mode 100644 index 0000000..161d08f --- /dev/null +++ b/jadler-core/src/main/java/net/jadler/Duration.java @@ -0,0 +1,98 @@ +package net.jadler; + +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; +import org.apache.commons.lang.builder.ToStringBuilder; +import java.util.concurrent.TimeUnit; + +/** + * Simple class to represent a Duration in some timeunit. It does this be + * holding the value as a long, and the type of timeunit as a TimeUnit enum value. + * + * When the library one day requires java8, this class should be removed, and replaced + * by java.time.Duration. + * + * This class is Immutable. + */ +public final class Duration { + + private static class ZeroHolder { + public static final Duration instance = Duration.ofNanos(0L); + } + + private final long value; + private final TimeUnit timeUnit; + + public static Duration zero() { + return ZeroHolder.instance; + } + + public static Duration ofNanos(final long value) { + return new Duration(value, TimeUnit.NANOSECONDS); + } + + public static Duration ofMillis(final long value) { + return new Duration(value, TimeUnit.MILLISECONDS); + } + + public static Duration ofSeconds(final long value) { + return new Duration(value, TimeUnit.SECONDS); + } + + public static Duration ofMinutes(final long value) { + return new Duration(value, TimeUnit.MINUTES); + } + + private Duration(final long value, final TimeUnit timeUnit) { + if (value < 0) + throw new IllegalArgumentException("Value of a Duratino cannot be negative"); + this.value = value; + this.timeUnit = timeUnit; + } + + public long getValue() { + return value; + } + + public TimeUnit getTimeUnit() { + return timeUnit; + } + + public long toNanos() { + return this.timeUnit.toNanos(this.value); + } + + public boolean isZero() { + return this.value == 0; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (!(o instanceof Duration)) return false; + + Duration duration = (Duration) o; + + return new EqualsBuilder() + .append(value, duration.value) + .append(timeUnit, duration.timeUnit) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(value) + .append(timeUnit) + .toHashCode(); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("value", value) + .append("timeUnit", timeUnit) + .toString(); + } +} diff --git a/jadler-core/src/main/java/net/jadler/JadlerMocker.java b/jadler-core/src/main/java/net/jadler/JadlerMocker.java index 9617d5a..e807b47 100644 --- a/jadler-core/src/main/java/net/jadler/JadlerMocker.java +++ b/jadler-core/src/main/java/net/jadler/JadlerMocker.java @@ -7,7 +7,8 @@ import net.jadler.stubbing.Stubber; import net.jadler.stubbing.server.StubHttpServerManager; import java.nio.charset.Charset; -import java.util.ArrayList; +import java.util.*; + import net.jadler.stubbing.RequestStubbing; import net.jadler.stubbing.StubbingFactory; import net.jadler.stubbing.Stubbing; @@ -15,11 +16,10 @@ import net.jadler.stubbing.HttpStub; import net.jadler.exception.JadlerException; import net.jadler.stubbing.server.StubHttpServer; -import java.util.Collection; -import java.util.Deque; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + import net.jadler.mocking.Mocker; import net.jadler.mocking.VerificationException; import net.jadler.mocking.Verifying; @@ -56,6 +56,7 @@ public class JadlerMocker implements StubHttpServerManager, Stubber, RequestMana private final List stubbings; private Deque httpStubs; private final List receivedRequests; + private final Set asyncVerificators; private MultiMap defaultHeaders; private int defaultStatus; @@ -75,6 +76,19 @@ public class JadlerMocker implements StubHttpServerManager, Stubber, RequestMana } private static final Logger logger = LoggerFactory.getLogger(JadlerMocker.class); + + /** + * The meaning of this class, is to wrpap the BlockingQueue inside an object, + * where the equals method only equals on exactly the same objects, so that each + * queue can be added to a set, and later removed again. + */ + private static final class AsyncVerificator { + private final BlockingQueue requestqueue; + + public AsyncVerificator() { + this.requestqueue = new LinkedBlockingQueue(); + } + } /** @@ -109,6 +123,7 @@ public JadlerMocker(final StubHttpServer server) { this.httpStubs = new LinkedList(); this.receivedRequests = new ArrayList(); + this.asyncVerificators = new HashSet(); } /** @@ -237,6 +252,9 @@ public StubResponse provideStubResponseFor(final Request request) { if (this.recordRequests) { this.receivedRequests.add(request); + for (AsyncVerificator asyncVerificator : this.asyncVerificators) { + asyncVerificator.requestqueue.offer(request); + } } } @@ -305,15 +323,12 @@ public int numberOfRequestsMatching(final Collection> p @Override public void evaluateVerification(final Collection> requestPredicates, final Matcher nrRequestsPredicate) { - - Validate.notNull(requestPredicates, "requestPredicates cannot be null"); - Validate.notNull(nrRequestsPredicate, "nrRequestsPredicate cannot be null"); - - this.checkRequestRecording(); - + + validateEvaluateVerificationArgsAndState(requestPredicates, nrRequestsPredicate); + synchronized(this) { final int cnt = this.numberOfRequestsMatching(requestPredicates); - + if (!nrRequestsPredicate.matches(cnt)) { this.logReceivedRequests(requestPredicates); throw new VerificationException(this.mismatchDescription(cnt, requestPredicates, nrRequestsPredicate)); @@ -321,7 +336,79 @@ public void evaluateVerification(final Collection> requ } } - + @Override + public void evaluateVerificationAsync( + final Collection> requestPredicates, + final Matcher nrRequestsPredicate, + final Duration timeOut) { + + validateEvaluateVerificationArgsAndState(requestPredicates, nrRequestsPredicate); + Validate.notNull(timeOut, "timeUnit cannot be null"); + + final long startTime = System.nanoTime(); + final AsyncVerificator myQueue = new AsyncVerificator(); + int cnt = 0; + + cnt = this.numberOfRequestsMatching(requestPredicates); + if (nrRequestsPredicate.matches(cnt)) { + return; + } + synchronized(this) { this.asyncVerificators.add(myQueue); } + + Duration timeLeft = calculateTimeLeft(startTime, timeOut); + + while (!timeLeft.isZero()) { + try { + myQueue.requestqueue.poll(timeLeft.getValue(), timeLeft.getTimeUnit()); + cnt = this.numberOfRequestsMatching(requestPredicates); + if (nrRequestsPredicate.matches(cnt)) { + return; + } else { + timeLeft = calculateTimeLeft(startTime, timeOut); + } + } catch (InterruptedException e) { + timeLeft = calculateTimeLeft(startTime, timeOut); + } + } + + // If it reach here, then the time is up, and we should fail. + failAsyncVerification(myQueue, requestPredicates, cnt, nrRequestsPredicate); + } + + private void validateEvaluateVerificationArgsAndState( + final Collection> requestPredicates, + final Matcher nrRequestsPredicate) { + Validate.notNull(requestPredicates, "requestPredicates cannot be null"); + Validate.notNull(nrRequestsPredicate, "nrRequestsPredicate cannot be null"); + this.checkRequestRecording(); + } + + private void failAsyncVerification( + final AsyncVerificator a, + final Collection> requestPredicates, + final int cnt, + final Matcher nrRequestsPredicate) { + removeFromAsyncVerificators(a); + this.logReceivedRequests(requestPredicates); + throw new VerificationException(this.mismatchDescription(cnt, requestPredicates, nrRequestsPredicate)); + } + + private synchronized void removeFromAsyncVerificators(final AsyncVerificator a) { + this.asyncVerificators.remove(a); + } + + private Duration calculateTimeLeft( + final long startTimeInNanos, + final Duration dur) { + final long now = System.nanoTime(); + if (startTimeInNanos + dur.toNanos() < now) + return Duration.zero(); + else { + final long left = (startTimeInNanos + dur.toNanos()) - now; + return Duration.ofNanos(left); + } + } + /** *

Resets this mocker instance so it can be reused. This method clears all previously created stubs as well as * stored received requests (for mocking purpose, @@ -427,30 +514,29 @@ private Deque createHttpStubs() { private void logReceivedRequests(final Collection> requestPredicates) { final StringBuilder sb = new StringBuilder("Verification failed, here is a list of requests received so far:"); - this.appendNoneIfEmpty(this.receivedRequests, sb); int pos = 1; - for (final Request req: this.receivedRequests) { - sb.append("\n"); - final Collection> matching = new ArrayList>(); - final Collection> clashing = new ArrayList>(); - - for (final Matcher pred: requestPredicates) { - if (pred.matches(req)) { - matching.add(pred); - } - else { - clashing.add(pred); + synchronized (this) { + this.appendNoneIfEmpty(this.receivedRequests, sb); + for (final Request req: this.receivedRequests) { + sb.append("\n"); + final Collection> matching = new ArrayList>(); + final Collection> clashing = new ArrayList>(); + + for (final Matcher pred: requestPredicates) { + if (pred.matches(req)) { + matching.add(pred); + } + else { + clashing.add(pred); + } } + + this.appendReason(sb, req, pos, matching, clashing); + + pos++; } - - this.appendReason(sb, req, pos, matching, clashing); - - pos++; } - - - logger.info(sb.toString()); } @@ -536,4 +622,4 @@ private synchronized void checkRequestRecording() { throw new IllegalStateException("Request recording is switched off, cannot do any request verification"); } } -} \ No newline at end of file +} diff --git a/jadler-core/src/main/java/net/jadler/RequestManager.java b/jadler-core/src/main/java/net/jadler/RequestManager.java index 351af1b..8fd0df3 100644 --- a/jadler-core/src/main/java/net/jadler/RequestManager.java +++ b/jadler-core/src/main/java/net/jadler/RequestManager.java @@ -5,6 +5,8 @@ package net.jadler; import java.util.Collection; +import java.util.concurrent.TimeUnit; + import net.jadler.stubbing.StubResponse; import org.hamcrest.Matcher; @@ -37,8 +39,11 @@ public interface RequestManager { */ void evaluateVerification(Collection> requestPredicates, Matcher nrRequestsPredicate); - - + + void evaluateVerificationAsync(Collection> requestPredicates, + Matcher nrRequestsPredicate, + Duration timeOut); + /** * @deprecated this (rather internal) method has been deprecated. Please use * {@link #evaluateVerification(java.util.Collection, org.hamcrest.Matcher)} instead @@ -49,4 +54,4 @@ void evaluateVerification(Collection> requestPredicates */ @Deprecated int numberOfRequestsMatching(Collection> predicates); -} \ No newline at end of file +} diff --git a/jadler-core/src/main/java/net/jadler/mocking/Verifying.java b/jadler-core/src/main/java/net/jadler/mocking/Verifying.java index c8e8768..44ba219 100644 --- a/jadler-core/src/main/java/net/jadler/mocking/Verifying.java +++ b/jadler-core/src/main/java/net/jadler/mocking/Verifying.java @@ -5,6 +5,7 @@ package net.jadler.mocking; import net.jadler.AbstractRequestMatching; +import net.jadler.Duration; import net.jadler.RequestManager; import org.apache.commons.lang.Validate; import org.hamcrest.Matcher; @@ -43,6 +44,25 @@ public void receivedTimes(final Matcher nrRequestsPredicate) { this.requestManager.evaluateVerification(predicates, nrRequestsPredicate); } + + /** + * Checks whether the number of requests described in this verifying object received + * so far, or within the supplied timeout matches the given predicate. + * + * @param nrRequestsPredicate to be applied on the number of requests + * @param timeOut time to wait for the expected requst to be made, before failing the check. + * @throws VerificationException if the number of requests described by this verifying is not matched by the given + * predicate + */ + public void receivedTimes( + final Matcher nrRequestsPredicate, + final Duration timeOut) { + Validate.notNull(nrRequestsPredicate, "predicate cannot be null"); + Validate.notNull(timeOut, "timeOut cannot be null"); + + this.requestManager.evaluateVerificationAsync( + predicates, nrRequestsPredicate, timeOut); + } /** @@ -55,6 +75,22 @@ public void receivedTimes(final int count) { Validate.isTrue(count >= 0, "count cannot be negative"); this.receivedTimes(equalTo(count)); } + + /** + * Checks whether the number of requests described in this verifying object received + * so far, or within the supplied timeout matches the exact value. + * + * @param count expected number of requests described by this verifying object + * @param timeOut time to wait for the expected requst to be made, before failing the check. + * @throws VerificationException if the number of requests described in this verifying object received so far + * is not equal to the expected value + */ + public void receivedTimes( + final int count, + final Duration timeOut) { + Validate.isTrue(count >= 0, "count cannot be negative"); + this.receivedTimes(equalTo(count), timeOut); + } /** @@ -64,6 +100,17 @@ public void receivedTimes(final int count) { public void receivedOnce() { this.receivedTimes(1); } + + /** + * Checks that exactly one request described in this verifying object has been + * received so far or within the supplied timeout. + * + * @param timeOut time to wait for the expected requst to be made, before failing the check. + * @throws VerificationException if the number of expected requests is not equal to one + */ + public void receivedOnce(final Duration timeOut) { + this.receivedTimes(1, timeOut); + } /** @@ -73,4 +120,4 @@ public void receivedOnce() { public void receivedNever() { this.receivedTimes(0); } -} \ No newline at end of file +} diff --git a/jadler-core/src/test/java/net/jadler/JadlerMockerEvaluateVerificationXTest.java b/jadler-core/src/test/java/net/jadler/JadlerMockerEvaluateVerificationXTest.java new file mode 100644 index 0000000..5edd9b0 --- /dev/null +++ b/jadler-core/src/test/java/net/jadler/JadlerMockerEvaluateVerificationXTest.java @@ -0,0 +1,468 @@ +package net.jadler; + +import net.jadler.mocking.VerificationException; +import net.jadler.stubbing.server.StubHttpServer; +import org.hamcrest.Matcher; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.io.Writer; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +import static java.lang.String.format; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Test class for all cases where JadlerMocker.evaluateVerificationAsync + * and Verifying.evaluateVerificationAsync should behave the same. + * + * When your create test here, your should never call the evaluateVerification method + * directly, but instead though the evaluateVerificationCaller field, as this makes + * sure that the test case is run ones where calling evaluateVerification and once where + * calling evaluateVerificationAsyn. The timeout parameter will need to be supplied, but + * will just not be used in the case where evaluateVerification is used. + */ +@RunWith(Parameterized.class) +public class JadlerMockerEvaluateVerificationXTest extends JadlerMockerTestBase { + + private static interface EvaluateVerificationCaller { + void callOn( + final JadlerMocker instance, + final Collection> requestPredicates, + final Matcher nrRequestsPredicate, + final Duration timeOut); + } + + @Parameterized.Parameters + public static Collection data() { + return Arrays.asList(new Object[][] { + {new EvaluateVerificationCaller() { + @Override + public void callOn( + JadlerMocker instance, + Collection> p, + Matcher nrP, + Duration timeOut) { + instance.evaluateVerification(p, nrP); + } + }}, + {new EvaluateVerificationCaller() { + @Override + public void callOn( + JadlerMocker instance, + Collection> p, + Matcher nrP, + Duration timeOut) { + instance.evaluateVerificationAsync(p, nrP, timeOut); + } + }}}); + } + + private final EvaluateVerificationCaller evaluateVerificationCaller; + + public JadlerMockerEvaluateVerificationXTest( + EvaluateVerificationCaller evaluateVerificationCaller) { + this.evaluateVerificationCaller = evaluateVerificationCaller; + } + + + @Test(expected = IllegalArgumentException.class) + @SuppressWarnings("unchecked") + public void evaluateVerification_illegalArgument1() { + final JadlerMocker instance = new JadlerMocker(mock(StubHttpServer.class)); + evaluateVerificationCaller.callOn( + instance, null, mock(Matcher.class), Duration.zero()); + } + + + @Test(expected = IllegalArgumentException.class) + @SuppressWarnings("unchecked") + public void evaluateVerification_illegalArgument2() { + final JadlerMocker instance = new JadlerMocker(mock(StubHttpServer.class)); + evaluateVerificationCaller.callOn( + instance, Collections.>singleton(mock(Matcher.class)), + null, + Duration.zero()); + } + + @Test(expected = IllegalStateException.class) + @SuppressWarnings("unchecked") + public void evaluateVerification_recordingDisabled() { + final JadlerMocker mocker = new JadlerMocker(mock(StubHttpServer.class)); + mocker.setRecordRequests(false); + + evaluateVerificationCaller.callOn( + mocker, + Collections.>singleton(mock(Matcher.class)), + mock(Matcher.class), Duration.zero()); + } + + @Test + public void evaluateVerification_2_matching_positive() { + final JadlerMocker mocker = this.createMockerWithRequests(); + + final Matcher m1 = this.createRequestMatcher(MATCHER1_DESCRIPTION, MATCHER1_MISMATCH); + final Matcher m2 = this.createRequestMatcher(MATCHER2_DESCRIPTION, MATCHER2_MISMATCH); + + //R0 is not matched + when(m1.matches(eq(REQUESTS[0]))).thenReturn(false); + when(m2.matches(eq(REQUESTS[0]))).thenReturn(false); + + //R1 is matched + when(m1.matches(eq(REQUESTS[1]))).thenReturn(true); + when(m2.matches(eq(REQUESTS[1]))).thenReturn(true); + + //R2 is not matched + when(m1.matches(eq(REQUESTS[2]))).thenReturn(false); + when(m2.matches(eq(REQUESTS[2]))).thenReturn(true); + + //R3 is not matched + when(m1.matches(eq(REQUESTS[3]))).thenReturn(true); + when(m2.matches(eq(REQUESTS[3]))).thenReturn(false); + + //R4 is matched + when(m1.matches(eq(REQUESTS[4]))).thenReturn(true); + when(m2.matches(eq(REQUESTS[4]))).thenReturn(true); + + //2 requests matching, 2 expected + final Matcher countMatcher = this.createCountMatcherFor(2, 2, COUNT_MATCHER_DESCRIPTION, + COUNT_MATCHER_MISMATCH); + + evaluateVerificationCaller.callOn( + mocker, collectionOf(m1, m2), countMatcher, Duration.zero()); + } + + @Test(expected = VerificationException.class) + public void evaluateVerification_2_matching_expectind3_WithNoneZeroTimeout_negative() { + final JadlerMocker mocker = this.createMockerWithRequests(); + + final Matcher m1 = this.createRequestMatcher(MATCHER1_DESCRIPTION, MATCHER1_MISMATCH); + final Matcher m2 = this.createRequestMatcher(MATCHER2_DESCRIPTION, MATCHER2_MISMATCH); + + //R0 is not matched + when(m1.matches(eq(REQUESTS[0]))).thenReturn(false); + when(m2.matches(eq(REQUESTS[0]))).thenReturn(false); + + //R1 is matched + when(m1.matches(eq(REQUESTS[1]))).thenReturn(true); + when(m2.matches(eq(REQUESTS[1]))).thenReturn(true); + + //R2 is not matched + when(m1.matches(eq(REQUESTS[2]))).thenReturn(false); + when(m2.matches(eq(REQUESTS[2]))).thenReturn(true); + + //R3 is not matched + when(m1.matches(eq(REQUESTS[3]))).thenReturn(true); + when(m2.matches(eq(REQUESTS[3]))).thenReturn(false); + + //R4 is matched + when(m1.matches(eq(REQUESTS[4]))).thenReturn(true); + when(m2.matches(eq(REQUESTS[4]))).thenReturn(true); + + //2 requests matching, 3 expected + final Matcher countMatcher = this.createCountMatcherFor(3, 2, COUNT_MATCHER_DESCRIPTION, + COUNT_MATCHER_MISMATCH); + + evaluateVerificationCaller.callOn( + mocker, collectionOf(m1, m2), countMatcher, Duration.ofNanos(1)); + } + + @Test + public void evaluateVerification_0_matching_positive() { + final JadlerMocker mocker = this.createMockerWithRequests(); + + final Matcher m1 = this.createRequestMatcher(MATCHER1_DESCRIPTION, MATCHER1_MISMATCH); + final Matcher m2 = this.createRequestMatcher(MATCHER2_DESCRIPTION, MATCHER2_MISMATCH); + + //R0 is not matched + when(m1.matches(eq(REQUESTS[0]))).thenReturn(false); + when(m2.matches(eq(REQUESTS[0]))).thenReturn(false); + + //R1 is not matched + when(m1.matches(eq(REQUESTS[1]))).thenReturn(true); + when(m2.matches(eq(REQUESTS[1]))).thenReturn(false); + + //R2 is not matched + when(m1.matches(eq(REQUESTS[2]))).thenReturn(false); + when(m2.matches(eq(REQUESTS[2]))).thenReturn(true); + + //R3 is not matched + when(m1.matches(eq(REQUESTS[3]))).thenReturn(true); + when(m2.matches(eq(REQUESTS[3]))).thenReturn(false); + + //R4 is not matched + when(m1.matches(eq(REQUESTS[4]))).thenReturn(false); + when(m2.matches(eq(REQUESTS[4]))).thenReturn(true); + + //0 requests matching, 0 expected + final Matcher countMatcher = this.createCountMatcherFor(0, 0, COUNT_MATCHER_DESCRIPTION, + COUNT_MATCHER_MISMATCH); + + evaluateVerificationCaller.callOn( + mocker, collectionOf(m1, m2), countMatcher, Duration.zero()); + } + + @Test + public void evaluateVerification_0_predicates_5_matching_positive() { + final JadlerMocker mocker = this.createMockerWithRequests(); + + //5 requests matching (=received in this case), 5 expected + final int actualCount = 5; + final Matcher countMatcher = this.createCountMatcherFor(5, actualCount, COUNT_MATCHER_DESCRIPTION, + COUNT_MATCHER_MISMATCH); + + evaluateVerificationCaller.callOn( + mocker, + Collections.>emptySet(), + countMatcher, + Duration.zero()); + } + + @Test + public void evaluateVerification_0_requests_0_matching_positive() { + final JadlerMocker mocker = new JadlerMocker(mock(StubHttpServer.class)); + final Matcher m1 = this.createRequestMatcher(MATCHER1_DESCRIPTION, MATCHER1_MISMATCH); + final Matcher m2 = this.createRequestMatcher(MATCHER2_DESCRIPTION, MATCHER2_MISMATCH); + + //0 requests matching (=received in this case), 0 expected + final Matcher countMatcher = this.createCountMatcherFor(0, 0, COUNT_MATCHER_DESCRIPTION, + COUNT_MATCHER_MISMATCH); + + evaluateVerificationCaller.callOn( + mocker, collectionOf(m1, m2), countMatcher, Duration.zero()); + } + + @Test + public void evaluateVerification_2_matching_negative() { + final JadlerMocker mocker = this.createMockerWithRequests(); + + final Matcher m1 = this.createRequestMatcher(MATCHER1_DESCRIPTION, MATCHER1_MISMATCH); + final Matcher m2 = this.createRequestMatcher(MATCHER2_DESCRIPTION, MATCHER2_MISMATCH); + + //R0 is not matched + when(m1.matches(eq(REQUESTS[0]))).thenReturn(false); + when(m2.matches(eq(REQUESTS[0]))).thenReturn(false); + + //R1 is matched + when(m1.matches(eq(REQUESTS[1]))).thenReturn(true); + when(m2.matches(eq(REQUESTS[1]))).thenReturn(true); + + //R2 is not matched + when(m1.matches(eq(REQUESTS[2]))).thenReturn(false); + when(m2.matches(eq(REQUESTS[2]))).thenReturn(true); + + //R3 is not matched + when(m1.matches(eq(REQUESTS[3]))).thenReturn(true); + when(m2.matches(eq(REQUESTS[3]))).thenReturn(false); + + //R4 is matched + when(m1.matches(eq(REQUESTS[4]))).thenReturn(true); + when(m2.matches(eq(REQUESTS[4]))).thenReturn(true); + + //2 requests matching, 1 expected + final Matcher countMatcher = this.createCountMatcherFor(1, 2, COUNT_MATCHER_DESCRIPTION, + COUNT_MATCHER_MISMATCH); + + final Writer w = this.createAppenderWriter(); + try { + evaluateVerificationCaller.callOn( + mocker, collectionOf(m1, m2), countMatcher, Duration.zero()); + fail("VerificationException is supposed to be thrown here"); + } + catch (final VerificationException e) { + assertThat(e.getMessage(), is(expectedMessage())); + assertThat(w.toString(), is(format( + "[INFO] Verification failed, here is a list of requests received so far:\n" + + "Request #1: %s\n" + + " matching predicates: \n" + + " clashing predicates:\n" + + " %s\n" + + " %s\n" + + "Request #2: %s\n" + + " matching predicates:\n" + + " %s\n" + + " %s\n" + + " clashing predicates: \n" + + "Request #3: %s\n" + + " matching predicates:\n" + + " %s\n" + + " clashing predicates:\n" + + " %s\n" + + "Request #4: %s\n" + + " matching predicates:\n" + + " %s\n" + + " clashing predicates:\n" + + " %s\n" + + "Request #5: %s\n" + + " matching predicates:\n" + + " %s\n" + + " %s\n" + + " clashing predicates: ", + REQUESTS[0], MATCHER1_MISMATCH, MATCHER2_MISMATCH, + REQUESTS[1], MATCHER1_DESCRIPTION, MATCHER2_DESCRIPTION, + REQUESTS[2], MATCHER2_DESCRIPTION, MATCHER1_MISMATCH, + REQUESTS[3], MATCHER1_DESCRIPTION, MATCHER2_MISMATCH, + REQUESTS[4], MATCHER1_DESCRIPTION, MATCHER2_DESCRIPTION))); + } + finally { + this.clearLog4jSetup(); + } + } + + @Test + public void evaluateVerification_0_matching_negative() { + final JadlerMocker mocker = this.createMockerWithRequests(); + + final Matcher m1 = this.createRequestMatcher(MATCHER1_DESCRIPTION, MATCHER1_MISMATCH); + final Matcher m2 = this.createRequestMatcher(MATCHER2_DESCRIPTION, MATCHER2_MISMATCH); + + //R0 is not matched + when(m1.matches(eq(REQUESTS[0]))).thenReturn(false); + when(m2.matches(eq(REQUESTS[0]))).thenReturn(false); + + //R1 is not matched + when(m1.matches(eq(REQUESTS[1]))).thenReturn(false); + when(m2.matches(eq(REQUESTS[1]))).thenReturn(true); + + //R2 is not matched + when(m1.matches(eq(REQUESTS[2]))).thenReturn(false); + when(m2.matches(eq(REQUESTS[2]))).thenReturn(true); + + //R3 is not matched + when(m1.matches(eq(REQUESTS[3]))).thenReturn(true); + when(m2.matches(eq(REQUESTS[3]))).thenReturn(false); + + //R4 is not matched + when(m1.matches(eq(REQUESTS[4]))).thenReturn(true); + when(m2.matches(eq(REQUESTS[4]))).thenReturn(false); + + //0 requests matching, 1 expected + final Matcher countMatcher = this.createCountMatcherFor(1, 0, COUNT_MATCHER_DESCRIPTION, + COUNT_MATCHER_MISMATCH); + + final Writer w = this.createAppenderWriter(); + try { + evaluateVerificationCaller.callOn( + mocker, collectionOf(m1, m2), countMatcher, Duration.zero()); + fail("VerificationException is supposed to be thrown here"); + } + catch (final VerificationException e) { + assertThat(e.getMessage(), is(expectedMessage())); + assertThat(w.toString(), is(format( + "[INFO] Verification failed, here is a list of requests received so far:\n" + + "Request #1: %s\n" + + " matching predicates: \n" + + " clashing predicates:\n" + + " %s\n" + + " %s\n" + + "Request #2: %s\n" + + " matching predicates:\n" + + " %s\n" + + " clashing predicates:\n" + + " %s\n" + + "Request #3: %s\n" + + " matching predicates:\n" + + " %s\n" + + " clashing predicates:\n" + + " %s\n" + + "Request #4: %s\n" + + " matching predicates:\n" + + " %s\n" + + " clashing predicates:\n" + + " %s\n" + + "Request #5: %s\n" + + " matching predicates:\n" + + " %s\n" + + " clashing predicates:\n" + + " %s", + REQUESTS[0], MATCHER1_MISMATCH, MATCHER2_MISMATCH, + REQUESTS[1], MATCHER2_DESCRIPTION, MATCHER1_MISMATCH, + REQUESTS[2], MATCHER2_DESCRIPTION, MATCHER1_MISMATCH, + REQUESTS[3], MATCHER1_DESCRIPTION, MATCHER2_MISMATCH, + REQUESTS[4], MATCHER1_DESCRIPTION, MATCHER2_MISMATCH))); + } + finally { + this.clearLog4jSetup(); + } + } + + @Test + public void evaluateVerification_0_predicates_5_matching_negative() { + final JadlerMocker mocker = this.createMockerWithRequests(); + + //5 requests matching (=received in this case), 4 expected + final Matcher countMatcher = this.createCountMatcherFor(4, 5, COUNT_MATCHER_DESCRIPTION, + COUNT_MATCHER_MISMATCH); + + final Writer w = this.createAppenderWriter(); + try { + evaluateVerificationCaller.callOn( + mocker, + Collections.>emptySet(), + countMatcher, + Duration.zero()); + fail("VerificationException is supposed to be thrown here"); + } + catch (final VerificationException e) { + assertThat(e.getMessage(), is(format("The number of http requests was expected to be %s, but %s", + COUNT_MATCHER_DESCRIPTION, COUNT_MATCHER_MISMATCH))); + + assertThat(w.toString(), is(format( + "[INFO] Verification failed, here is a list of requests received so far:\n" + + "Request #1: %s\n" + + " matching predicates: \n" + + " clashing predicates: \n" + + "Request #2: %s\n" + + " matching predicates: \n" + + " clashing predicates: \n" + + "Request #3: %s\n" + + " matching predicates: \n" + + " clashing predicates: \n" + + "Request #4: %s\n" + + " matching predicates: \n" + + " clashing predicates: \n" + + "Request #5: %s\n" + + " matching predicates: \n" + + " clashing predicates: ", + REQUESTS[0], REQUESTS[1], REQUESTS[2], REQUESTS[3], REQUESTS[4]))); + } + finally { + this.clearLog4jSetup(); + } + } + + @Test + public void evaluateVerification_0_requests_0_matching_negative() { + final JadlerMocker mocker = new JadlerMocker(mock(StubHttpServer.class)); + final Matcher m1 = this.createRequestMatcher(MATCHER1_DESCRIPTION, MATCHER1_MISMATCH); + final Matcher m2 = this.createRequestMatcher(MATCHER2_DESCRIPTION, MATCHER2_MISMATCH); + + //0 requests matching (=received in this case), 0 expected + final Matcher countMatcher = this.createCountMatcherFor(1, 0, COUNT_MATCHER_DESCRIPTION, + COUNT_MATCHER_MISMATCH); + + final Writer w = this.createAppenderWriter(); + + try { + evaluateVerificationCaller.callOn( + mocker, collectionOf(m1, m2), countMatcher, Duration.zero()); + fail("VerificationException is supposed to be thrown here"); + } + catch (final VerificationException e) { + assertThat(e.getMessage(), is(expectedMessage())); + assertThat(w.toString(), + is("[INFO] Verification failed, here is a list of requests received so far: ")); + } + finally { + this.clearLog4jSetup(); + } + } +} diff --git a/jadler-core/src/test/java/net/jadler/JadlerMockerTest.java b/jadler-core/src/test/java/net/jadler/JadlerMockerTest.java index 9826bab..4bdef3e 100644 --- a/jadler-core/src/test/java/net/jadler/JadlerMockerTest.java +++ b/jadler-core/src/test/java/net/jadler/JadlerMockerTest.java @@ -12,6 +12,8 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.concurrent.*; + import net.jadler.stubbing.Stubbing; import net.jadler.stubbing.HttpStub; import net.jadler.stubbing.StubResponse; @@ -28,6 +30,9 @@ import org.apache.log4j.WriterAppender; import org.hamcrest.Description; import org.hamcrest.Matcher; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; import org.junit.Test; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; @@ -50,33 +55,19 @@ import static org.hamcrest.Matchers.is; -public class JadlerMockerTest { - - private static final int DEFAULT_STATUS = 204; - private static final String HEADER_NAME1 = "h1"; - private static final String HEADER_VALUE1 = "v1"; - private static final String HEADER_NAME2 = "h2"; - private static final String HEADER_VALUE2 = "v2"; - private static final Charset DEFAULT_ENCODING = Charset.forName("UTF-16"); - private static final int PORT = 12345; - private static final String HTTP_STUB1_TO_STRING = "http stub 1 toString"; - private static final String HTTP_STUB2_TO_STRING = "http stub 2 toString"; - private static final String HTTP_STUB1_MISMATCH = "http stub 1 mismatch"; - private static final String HTTP_STUB2_MISMATCH = "http stub 2 mismatch"; - private static final String MATCHER1_DESCRIPTION = "M1 description"; - private static final String MATCHER1_MISMATCH = "M1 mismatch"; - private static final String MATCHER2_DESCRIPTION = "M2 description"; - private static final String MATCHER2_MISMATCH = "M2 mismatch"; - private static final String COUNT_MATCHER_DESCRIPTION = "cnt matcher description"; - private static final String COUNT_MATCHER_MISMATCH = "cnt matcher mismatch"; - - private static final Request[] REQUESTS = new Request[] { - Request.builder().method("GET").requestURI(URI.create("/r1")).build(), - Request.builder().method("GET").requestURI(URI.create("/r2")).build(), - Request.builder().method("GET").requestURI(URI.create("/r3")).build(), - Request.builder().method("GET").requestURI(URI.create("/r4")).build(), - Request.builder().method("GET").requestURI(URI.create("/r5")).build()}; - +public class JadlerMockerTest extends JadlerMockerTestBase { + + private ScheduledExecutorService exec; + + @Before + public void before() { + exec = Executors.newScheduledThreadPool(10); + } + + @After + public void after() { + exec.shutdownNow(); + } @Test(expected=IllegalArgumentException.class) public void constructor1() { @@ -562,361 +553,133 @@ public void verifyThatRequest_noRequestRecording() { mocker.setRecordRequests(false); mocker.verifyThatRequest(); } - - - @Test - public void verifyThatRequest() { - final Verifying ongoingVerifying = new JadlerMocker(mock(StubHttpServer.class)).verifyThatRequest(); - assertThat(ongoingVerifying, is(not(nullValue()))); - } - - - @Test(expected = IllegalArgumentException.class) - @SuppressWarnings("unchecked") - public void evaluateVerification_illegalArgument1() { - new JadlerMocker(mock(StubHttpServer.class)).evaluateVerification(null, mock(Matcher.class)); - } - - + @Test(expected = IllegalArgumentException.class) - @SuppressWarnings("unchecked") - public void evaluateVerification_illegalArgument2() { - new JadlerMocker(mock(StubHttpServer.class)).evaluateVerification( - Collections.>singleton(mock(Matcher.class)), null); - } - - - @Test(expected = IllegalStateException.class) - @SuppressWarnings("unchecked") - public void evaluateVerification_recordingDisabled() { + public void evaluateVerificationAsyncNullDuration() { final JadlerMocker mocker = new JadlerMocker(mock(StubHttpServer.class)); - mocker.setRecordRequests(false); - - mocker.evaluateVerification(Collections.>singleton(mock(Matcher.class)), - mock(Matcher.class)); + mocker.evaluateVerificationAsync( + Collections.>singleton(mock(Matcher.class)), + mock(Matcher.class), + null); } - - + @Test - public void evaluateVerification_2_matching_positive() { + public void evaluateVerificationAsync_3_matching_positiveWhereTheLastRequestComesAfterCallToEvaluateVerificationAsync() throws ExecutionException, InterruptedException { final JadlerMocker mocker = this.createMockerWithRequests(); - + final Matcher m1 = this.createRequestMatcher(MATCHER1_DESCRIPTION, MATCHER1_MISMATCH); final Matcher m2 = this.createRequestMatcher(MATCHER2_DESCRIPTION, MATCHER2_MISMATCH); - - //R0 is not matched + + final Request lateRequest = Request.builder().method("GET").requestURI(URI.create("/lateRequest")).build(); + + //R0 is not matched when(m1.matches(eq(REQUESTS[0]))).thenReturn(false); when(m2.matches(eq(REQUESTS[0]))).thenReturn(false); - - //R1 is matched + + //R1 is matched when(m1.matches(eq(REQUESTS[1]))).thenReturn(true); when(m2.matches(eq(REQUESTS[1]))).thenReturn(true); - - //R2 is not matched + + //R2 is not matched when(m1.matches(eq(REQUESTS[2]))).thenReturn(false); when(m2.matches(eq(REQUESTS[2]))).thenReturn(true); - - //R3 is not matched + + //R3 is not matched when(m1.matches(eq(REQUESTS[3]))).thenReturn(true); when(m2.matches(eq(REQUESTS[3]))).thenReturn(false); - //R4 is matched + //R4 is matched when(m1.matches(eq(REQUESTS[4]))).thenReturn(true); when(m2.matches(eq(REQUESTS[4]))).thenReturn(true); - - //2 requests matching, 2 expected - final Matcher countMatcher = this.createCountMatcherFor(2, 2, COUNT_MATCHER_DESCRIPTION, - COUNT_MATCHER_MISMATCH); - - mocker.evaluateVerification(collectionOf(m1, m2), countMatcher); - } - - - @Test - public void evaluateVerification_0_matching_positive() { - final JadlerMocker mocker = this.createMockerWithRequests(); - - final Matcher m1 = this.createRequestMatcher(MATCHER1_DESCRIPTION, MATCHER1_MISMATCH); - final Matcher m2 = this.createRequestMatcher(MATCHER2_DESCRIPTION, MATCHER2_MISMATCH); - - //R0 is not matched - when(m1.matches(eq(REQUESTS[0]))).thenReturn(false); - when(m2.matches(eq(REQUESTS[0]))).thenReturn(false); - - //R1 is not matched - when(m1.matches(eq(REQUESTS[1]))).thenReturn(true); - when(m2.matches(eq(REQUESTS[1]))).thenReturn(false); - - //R2 is not matched - when(m1.matches(eq(REQUESTS[2]))).thenReturn(false); - when(m2.matches(eq(REQUESTS[2]))).thenReturn(true); - - //R3 is not matched - when(m1.matches(eq(REQUESTS[3]))).thenReturn(true); - when(m2.matches(eq(REQUESTS[3]))).thenReturn(false); - //R4 is not matched - when(m1.matches(eq(REQUESTS[4]))).thenReturn(false); - when(m2.matches(eq(REQUESTS[4]))).thenReturn(true); - - //0 requests matching, 0 expected - final Matcher countMatcher = this.createCountMatcherFor(0, 0, COUNT_MATCHER_DESCRIPTION, - COUNT_MATCHER_MISMATCH); - - mocker.evaluateVerification(collectionOf(m1, m2), countMatcher); - } - - - @Test - public void evaluateVerification_0_predicates_5_matching_positive() { - final JadlerMocker mocker = this.createMockerWithRequests(); - - //5 requests matching (=received in this case), 5 expected - final int actualCount = 5; - final Matcher countMatcher = this.createCountMatcherFor(5, actualCount, COUNT_MATCHER_DESCRIPTION, - COUNT_MATCHER_MISMATCH); - - mocker.evaluateVerification(Collections.>emptySet(), countMatcher); - } - - - @Test - public void evaluateVerification_0_requests_0_matching_positive() { - final JadlerMocker mocker = new JadlerMocker(mock(StubHttpServer.class)); - final Matcher m1 = this.createRequestMatcher(MATCHER1_DESCRIPTION, MATCHER1_MISMATCH); - final Matcher m2 = this.createRequestMatcher(MATCHER2_DESCRIPTION, MATCHER2_MISMATCH); - - //0 requests matching (=received in this case), 0 expected - final Matcher countMatcher = this.createCountMatcherFor(0, 0, COUNT_MATCHER_DESCRIPTION, + //lateRequest is matched + when(m1.matches(eq(lateRequest))).thenReturn(true); + when(m2.matches(eq(lateRequest))).thenReturn(true); + + //3 requests matching, 3 expected + final Matcher countMatcher = createCountMatcherFor(3, 3, COUNT_MATCHER_DESCRIPTION, COUNT_MATCHER_MISMATCH); - - mocker.evaluateVerification(collectionOf(m1, m2), countMatcher); + + final Future evaluateCall = exec.submit(new Callable() { + @Override + public String call() throws Exception { + mocker.evaluateVerificationAsync(collectionOf(m1, m2), countMatcher, Duration.ofSeconds(10)); + return "success"; + } + }); + exec.schedule(new Runnable() { + @Override + public void run() { + mocker.provideStubResponseFor(lateRequest); + } + }, 100, TimeUnit.MICROSECONDS); + evaluateCall.get(); } - - - @Test - public void evaluateVerification_2_matching_negative() { + + @Test (expected = VerificationException.class) + public void evaluateVerificationAsync_4_matching_Negative_WhereThirdRequestComesAfterCallToEvaluateVerificationAsyncButFourthRequestNeverComes() throws Throwable { final JadlerMocker mocker = this.createMockerWithRequests(); - + final Matcher m1 = this.createRequestMatcher(MATCHER1_DESCRIPTION, MATCHER1_MISMATCH); final Matcher m2 = this.createRequestMatcher(MATCHER2_DESCRIPTION, MATCHER2_MISMATCH); - - //R0 is not matched + + final Request lateRequest = Request.builder().method("GET").requestURI(URI.create("/lateRequest")).build(); + + //R0 is not matched when(m1.matches(eq(REQUESTS[0]))).thenReturn(false); when(m2.matches(eq(REQUESTS[0]))).thenReturn(false); - - //R1 is matched + + //R1 is matched when(m1.matches(eq(REQUESTS[1]))).thenReturn(true); when(m2.matches(eq(REQUESTS[1]))).thenReturn(true); - - //R2 is not matched + + //R2 is not matched when(m1.matches(eq(REQUESTS[2]))).thenReturn(false); when(m2.matches(eq(REQUESTS[2]))).thenReturn(true); - - //R3 is not matched + + //R3 is not matched when(m1.matches(eq(REQUESTS[3]))).thenReturn(true); when(m2.matches(eq(REQUESTS[3]))).thenReturn(false); - //R4 is matched + //R4 is matched when(m1.matches(eq(REQUESTS[4]))).thenReturn(true); when(m2.matches(eq(REQUESTS[4]))).thenReturn(true); - - //2 requests matching, 1 expected - final Matcher countMatcher = this.createCountMatcherFor(1, 2, COUNT_MATCHER_DESCRIPTION, - COUNT_MATCHER_MISMATCH); - - final Writer w = this.createAppenderWriter(); - try { - mocker.evaluateVerification(collectionOf(m1, m2), countMatcher); - fail("VerificationException is supposed to be thrown here"); - } - catch (final VerificationException e) { - assertThat(e.getMessage(), is(expectedMessage())); - assertThat(w.toString(), is(format( - "[INFO] Verification failed, here is a list of requests received so far:\n" + - "Request #1: %s\n" + - " matching predicates: \n" + - " clashing predicates:\n" + - " %s\n" + - " %s\n" + - "Request #2: %s\n" + - " matching predicates:\n" + - " %s\n" + - " %s\n" + - " clashing predicates: \n" + - "Request #3: %s\n" + - " matching predicates:\n" + - " %s\n" + - " clashing predicates:\n" + - " %s\n" + - "Request #4: %s\n" + - " matching predicates:\n" + - " %s\n" + - " clashing predicates:\n" + - " %s\n" + - "Request #5: %s\n" + - " matching predicates:\n" + - " %s\n" + - " %s\n" + - " clashing predicates: ", - REQUESTS[0], MATCHER1_MISMATCH, MATCHER2_MISMATCH, - REQUESTS[1], MATCHER1_DESCRIPTION, MATCHER2_DESCRIPTION, - REQUESTS[2], MATCHER2_DESCRIPTION, MATCHER1_MISMATCH, - REQUESTS[3], MATCHER1_DESCRIPTION, MATCHER2_MISMATCH, - REQUESTS[4], MATCHER1_DESCRIPTION, MATCHER2_DESCRIPTION))); - } - finally { - this.clearLog4jSetup(); - } - } - - - @Test - public void evaluateVerification_0_matching_negative() { - final JadlerMocker mocker = this.createMockerWithRequests(); - - final Matcher m1 = this.createRequestMatcher(MATCHER1_DESCRIPTION, MATCHER1_MISMATCH); - final Matcher m2 = this.createRequestMatcher(MATCHER2_DESCRIPTION, MATCHER2_MISMATCH); - - //R0 is not matched - when(m1.matches(eq(REQUESTS[0]))).thenReturn(false); - when(m2.matches(eq(REQUESTS[0]))).thenReturn(false); - - //R1 is not matched - when(m1.matches(eq(REQUESTS[1]))).thenReturn(false); - when(m2.matches(eq(REQUESTS[1]))).thenReturn(true); - - //R2 is not matched - when(m1.matches(eq(REQUESTS[2]))).thenReturn(false); - when(m2.matches(eq(REQUESTS[2]))).thenReturn(true); - - //R3 is not matched - when(m1.matches(eq(REQUESTS[3]))).thenReturn(true); - when(m2.matches(eq(REQUESTS[3]))).thenReturn(false); - //R4 is not matched - when(m1.matches(eq(REQUESTS[4]))).thenReturn(true); - when(m2.matches(eq(REQUESTS[4]))).thenReturn(false); - - //0 requests matching, 1 expected - final Matcher countMatcher = this.createCountMatcherFor(1, 0, COUNT_MATCHER_DESCRIPTION, - COUNT_MATCHER_MISMATCH); - - final Writer w = this.createAppenderWriter(); - try { - mocker.evaluateVerification(collectionOf(m1, m2), countMatcher); - fail("VerificationException is supposed to be thrown here"); - } - catch (final VerificationException e) { - assertThat(e.getMessage(), is(expectedMessage())); - assertThat(w.toString(), is(format( - "[INFO] Verification failed, here is a list of requests received so far:\n" + - "Request #1: %s\n" + - " matching predicates: \n" + - " clashing predicates:\n" + - " %s\n" + - " %s\n" + - "Request #2: %s\n" + - " matching predicates:\n" + - " %s\n" + - " clashing predicates:\n" + - " %s\n" + - "Request #3: %s\n" + - " matching predicates:\n" + - " %s\n" + - " clashing predicates:\n" + - " %s\n" + - "Request #4: %s\n" + - " matching predicates:\n" + - " %s\n" + - " clashing predicates:\n" + - " %s\n" + - "Request #5: %s\n" + - " matching predicates:\n" + - " %s\n" + - " clashing predicates:\n" + - " %s", - REQUESTS[0], MATCHER1_MISMATCH, MATCHER2_MISMATCH, - REQUESTS[1], MATCHER2_DESCRIPTION, MATCHER1_MISMATCH, - REQUESTS[2], MATCHER2_DESCRIPTION, MATCHER1_MISMATCH, - REQUESTS[3], MATCHER1_DESCRIPTION, MATCHER2_MISMATCH, - REQUESTS[4], MATCHER1_DESCRIPTION, MATCHER2_MISMATCH))); - } - finally { - this.clearLog4jSetup(); - } - } - - - @Test - public void evaluateVerification_0_predicates_5_matching_negative() { - final JadlerMocker mocker = this.createMockerWithRequests(); - - //5 requests matching (=received in this case), 4 expected - final Matcher countMatcher = this.createCountMatcherFor(4, 5, COUNT_MATCHER_DESCRIPTION, + //lateRequest is matched + when(m1.matches(eq(lateRequest))).thenReturn(true); + when(m2.matches(eq(lateRequest))).thenReturn(true); + + //3 requests matching, 3 expected + final Matcher countMatcher = createCountMatcherFor(4, 4, COUNT_MATCHER_DESCRIPTION, COUNT_MATCHER_MISMATCH); - - final Writer w = this.createAppenderWriter(); + + final Future evaluateCall = exec.submit(new Callable() { + @Override + public String call() throws Exception { + mocker.evaluateVerificationAsync(collectionOf(m1, m2), countMatcher, Duration.ofMillis(100)); + return "success"; + } + }); + exec.schedule(new Runnable() { + @Override + public void run() { + mocker.provideStubResponseFor(lateRequest); + } + }, 100, TimeUnit.MICROSECONDS); try { - mocker.evaluateVerification(Collections.>emptySet(), countMatcher); - fail("VerificationException is supposed to be thrown here"); - } - catch (final VerificationException e) { - assertThat(e.getMessage(), is(format("The number of http requests was expected to be %s, but %s", - COUNT_MATCHER_DESCRIPTION, COUNT_MATCHER_MISMATCH))); - - assertThat(w.toString(), is(format( - "[INFO] Verification failed, here is a list of requests received so far:\n" + - "Request #1: %s\n" + - " matching predicates: \n" + - " clashing predicates: \n" + - "Request #2: %s\n" + - " matching predicates: \n" + - " clashing predicates: \n" + - "Request #3: %s\n" + - " matching predicates: \n" + - " clashing predicates: \n" + - "Request #4: %s\n" + - " matching predicates: \n" + - " clashing predicates: \n" + - "Request #5: %s\n" + - " matching predicates: \n" + - " clashing predicates: ", - REQUESTS[0], REQUESTS[1], REQUESTS[2], REQUESTS[3], REQUESTS[4]))); - } - finally { - this.clearLog4jSetup(); + evaluateCall.get(); + } catch (ExecutionException e) { + throw e.getCause(); } + } - @Test - public void evaluateVerification_0_requests_0_matching_negative() { - final JadlerMocker mocker = new JadlerMocker(mock(StubHttpServer.class)); - final Matcher m1 = this.createRequestMatcher(MATCHER1_DESCRIPTION, MATCHER1_MISMATCH); - final Matcher m2 = this.createRequestMatcher(MATCHER2_DESCRIPTION, MATCHER2_MISMATCH); - - //0 requests matching (=received in this case), 0 expected - final Matcher countMatcher = this.createCountMatcherFor(1, 0, COUNT_MATCHER_DESCRIPTION, - COUNT_MATCHER_MISMATCH); - - final Writer w = this.createAppenderWriter(); - - try { - mocker.evaluateVerification(collectionOf(m1, m2), countMatcher); - fail("VerificationException is supposed to be thrown here"); - } - catch (final VerificationException e) { - assertThat(e.getMessage(), is(expectedMessage())); - assertThat(w.toString(), - is("[INFO] Verification failed, here is a list of requests received so far: ")); - } - finally { - this.clearLog4jSetup(); - } + public void verifyThatRequest() { + final Verifying ongoingVerifying = new JadlerMocker(mock(StubHttpServer.class)).verifyThatRequest(); + assertThat(ongoingVerifying, is(not(nullValue()))); } - @Test(expected=IllegalArgumentException.class) @SuppressWarnings("deprecation") public void numberOfRequestsMatchingInvalidArgument() { @@ -1003,110 +766,6 @@ private Request prepareEmptyMockRequest() { .build(); } - - private Matcher createRequestMatcher(final String desc, final String mismatch) { - @SuppressWarnings("unchecked") - final Matcher m = mock(Matcher.class); - - this.addDescriptionTo(m, desc); - - doAnswer(new Answer() { - @Override - public Void answer(InvocationOnMock invocation) throws Throwable { - final Description desc = (Description) invocation.getArguments()[1]; - desc.appendText(mismatch); - - return null; - } - }).when(m).describeMismatch(any(Request.class), any(Description.class)); - - return m; - } - - - /* - * Creates a Matcher instance to be used as a matcher of a number of requests during a verification - * @param expectedCount expected number of requests received so far which suit the verification description - * @param actualCount actual number of requests received so far which suit the verification description - * @param desc description of the matcher - * @param mismatch description of a mismatch - * @return a configured matcher - */ - private Matcher createCountMatcherFor(final int expectedCount, final int actualCount, - final String desc, final String mismatch) { - - @SuppressWarnings("unchecked") - final Matcher m = mock(Matcher.class); - - when(m.matches(eq(expectedCount))).thenReturn(true); - - doAnswer(new Answer() { - @Override - public Void answer(final InvocationOnMock invocation) throws Throwable { - final Description desc = (Description) invocation.getArguments()[1]; - desc.appendText(mismatch); - - return null; - } - }).when(m).describeMismatch(eq(actualCount), any(Description.class)); - - this.addDescriptionTo(m, desc); - return m; - } - - - private JadlerMocker createMockerWithRequests() { - final JadlerMocker mocker = new JadlerMocker(mock(StubHttpServer.class)); - - //register all requests - for (final Request r: REQUESTS) { - mocker.provideStubResponseFor(r); - } - - return mocker; - } - - - private void addDescriptionTo(final Matcher m, final String desc) { - doAnswer(new Answer() { - @Override - public Void answer(final InvocationOnMock invocation) throws Throwable { - final Description d = (Description) invocation.getArguments()[0]; - - d.appendText(desc); - - return null; - } - }).when(m).describeTo(any(Description.class)); - } - - - @SuppressWarnings("unchecked") - private Collection> collectionOf(Matcher m1, - Matcher m2) { - return Arrays.>asList(m1, m2); - } - - - private String expectedMessage() { - return format("The number of http requests having %s AND %s was expected to be %s, but %s", - MATCHER1_DESCRIPTION, MATCHER2_DESCRIPTION, COUNT_MATCHER_DESCRIPTION, COUNT_MATCHER_MISMATCH); - } - - - private Writer createAppenderWriter() { - final Writer w = new StringBuilderWriter(); - - final WriterAppender appender = new WriterAppender(); - appender.setLayout(new PatternLayout("[%p] %m")); - appender.setWriter(w); - - Logger.getRootLogger().addAppender(appender); - return w; - } - - - private void clearLog4jSetup() { - Logger.getRootLogger().getLoggerRepository().resetConfiguration(); - } + + } diff --git a/jadler-core/src/test/java/net/jadler/JadlerMockerTestBase.java b/jadler-core/src/test/java/net/jadler/JadlerMockerTestBase.java new file mode 100644 index 0000000..a18c323 --- /dev/null +++ b/jadler-core/src/test/java/net/jadler/JadlerMockerTestBase.java @@ -0,0 +1,156 @@ +package net.jadler; + +import net.jadler.stubbing.server.StubHttpServer; +import org.apache.commons.io.output.StringBuilderWriter; +import org.apache.log4j.Logger; +import org.apache.log4j.PatternLayout; +import org.apache.log4j.WriterAppender; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.io.Writer; +import java.net.URI; +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.Collection; + +import static java.lang.String.format; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Shared base for all JadlerMocker test classes. + */ +abstract class JadlerMockerTestBase { + protected static final int DEFAULT_STATUS = 204; + protected static final String HEADER_NAME1 = "h1"; + protected static final String HEADER_VALUE1 = "v1"; + protected static final String HEADER_NAME2 = "h2"; + protected static final String HEADER_VALUE2 = "v2"; + protected static final Charset DEFAULT_ENCODING = Charset.forName("UTF-16"); + protected static final int PORT = 12345; + protected static final String HTTP_STUB1_TO_STRING = "http stub 1 toString"; + protected static final String HTTP_STUB2_TO_STRING = "http stub 2 toString"; + protected static final String HTTP_STUB1_MISMATCH = "http stub 1 mismatch"; + protected static final String HTTP_STUB2_MISMATCH = "http stub 2 mismatch"; + protected static final String MATCHER1_DESCRIPTION = "M1 description"; + protected static final String MATCHER1_MISMATCH = "M1 mismatch"; + protected static final String MATCHER2_DESCRIPTION = "M2 description"; + protected static final String MATCHER2_MISMATCH = "M2 mismatch"; + protected static final String COUNT_MATCHER_DESCRIPTION = "cnt matcher description"; + protected static final String COUNT_MATCHER_MISMATCH = "cnt matcher mismatch"; + + protected static final Request[] REQUESTS = new Request[] { + Request.builder().method("GET").requestURI(URI.create("/r1")).build(), + Request.builder().method("GET").requestURI(URI.create("/r2")).build(), + Request.builder().method("GET").requestURI(URI.create("/r3")).build(), + Request.builder().method("GET").requestURI(URI.create("/r4")).build(), + Request.builder().method("GET").requestURI(URI.create("/r5")).build()}; + + + protected JadlerMocker createMockerWithRequests() { + final JadlerMocker mocker = new JadlerMocker(mock(StubHttpServer.class)); + + //register all requests + for (final Request r: REQUESTS) { + mocker.provideStubResponseFor(r); + } + + return mocker; + } + + protected Matcher createRequestMatcher(final String desc, final String mismatch) { + @SuppressWarnings("unchecked") + final Matcher m = mock(Matcher.class); + + this.addDescriptionTo(m, desc); + + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + final Description desc = (Description) invocation.getArguments()[1]; + desc.appendText(mismatch); + + return null; + } + }).when(m).describeMismatch(any(Request.class), any(Description.class)); + + return m; + } + + protected void addDescriptionTo(final Matcher m, final String desc) { + doAnswer(new Answer() { + @Override + public Void answer(final InvocationOnMock invocation) throws Throwable { + final Description d = (Description) invocation.getArguments()[0]; + + d.appendText(desc); + + return null; + } + }).when(m).describeTo(any(Description.class)); + } + + /* + * Creates a Matcher instance to be used as a matcher of a number of requests during a verification + * @param expectedCount expected number of requests received so far which suit the verification description + * @param actualCount actual number of requests received so far which suit the verification description + * @param desc description of the matcher + * @param mismatch description of a mismatch + * @return a configured matcher + */ + protected Matcher createCountMatcherFor(final int expectedCount, final int actualCount, + final String desc, final String mismatch) { + + @SuppressWarnings("unchecked") + final Matcher m = mock(Matcher.class); + + when(m.matches(eq(expectedCount))).thenReturn(true); + + doAnswer(new Answer() { + @Override + public Void answer(final InvocationOnMock invocation) throws Throwable { + final Description desc = (Description) invocation.getArguments()[1]; + desc.appendText(mismatch); + + return null; + } + }).when(m).describeMismatch(eq(actualCount), any(Description.class)); + + this.addDescriptionTo(m, desc); + return m; + } + + @SuppressWarnings("unchecked") + protected Collection> collectionOf( + Matcher m1, + Matcher m2) { + return Arrays.>asList(m1, m2); + } + + protected Writer createAppenderWriter() { + final Writer w = new StringBuilderWriter(); + + final WriterAppender appender = new WriterAppender(); + appender.setLayout(new PatternLayout("[%p] %m")); + appender.setWriter(w); + + Logger.getRootLogger().addAppender(appender); + return w; + } + + + protected void clearLog4jSetup() { + Logger.getRootLogger().getLoggerRepository().resetConfiguration(); + } + + protected String expectedMessage() { + return format("The number of http requests having %s AND %s was expected to be %s, but %s", + MATCHER1_DESCRIPTION, MATCHER2_DESCRIPTION, COUNT_MATCHER_DESCRIPTION, COUNT_MATCHER_MISMATCH); + } +} diff --git a/jadler-core/src/test/java/net/jadler/mocking/VerifyingTest.java b/jadler-core/src/test/java/net/jadler/mocking/VerifyingTest.java index 5dae1f3..4ce503e 100644 --- a/jadler-core/src/test/java/net/jadler/mocking/VerifyingTest.java +++ b/jadler-core/src/test/java/net/jadler/mocking/VerifyingTest.java @@ -6,6 +6,8 @@ import java.util.Arrays; import java.util.Collection; + +import net.jadler.Duration; import net.jadler.Request; import net.jadler.RequestManager; import org.hamcrest.Matcher; @@ -37,6 +39,8 @@ public class VerifyingTest { public void setUp() { doThrow(new VerificationException("")) .when(this.requestManager).evaluateVerification(anyCollection(), any(Matcher.class)); + doThrow(new VerificationException("")) + .when(this.requestManager).evaluateVerificationAsync(anyCollection(), any(Matcher.class), any(Duration.class)); } @@ -57,7 +61,12 @@ public void constructor() { public void receivedTimesMatcherIllegalArg() { new Verifying(requestManager).receivedTimes(null); } - + + @SuppressWarnings("unchecked") + @Test(expected=IllegalArgumentException.class) + public void receivedTimesMatcherNullDuration() { + new Verifying(requestManager).receivedTimes(mock(Matcher.class), null); + } @Test @SuppressWarnings("unchecked") @@ -82,6 +91,32 @@ public void receivedTimesMatcher_positive() { public void receivedTimesMatcher_negative() { new Verifying(requestManager).receivedTimes(mock(Matcher.class)); } + + @Test + @SuppressWarnings("unchecked") + public void receivedTimesMatcherWithDuration_positive() { + + final Matcher m1 = mock(Matcher.class); + final Matcher m2 = mock(Matcher.class); + + final Verifying v = new Verifying(requestManager).that(m1).that(m2); + + final Matcher pred = mock(Matcher.class); + final Collection> matchers = Arrays.>asList(m1, m2); + + doNothing().when(this.requestManager).evaluateVerificationAsync( + eq(matchers), eq(pred), eq(Duration.ofMillis(93L))); + + v.receivedTimes(pred, Duration.ofMillis(93L)); + } + + + @Test(expected=VerificationException.class) + @SuppressWarnings("unchecked") + public void receivedTimesMatcherWithDuration_negative() { + new Verifying(requestManager).receivedTimes( + mock(Matcher.class), Duration.ofMillis(33L)); + } @Test(expected=IllegalArgumentException.class) @@ -110,7 +145,33 @@ public void receivedTimesInt() { public void receivedTimesInt_negative() { new Verifying(requestManager).receivedTimes(42); } - + + @Test + @SuppressWarnings("unchecked") + public void receivedTimesIntWithDuration() { + + final Matcher m1 = mock(Matcher.class); + final Matcher m2 = mock(Matcher.class); + final Collection> matchers = Arrays.>asList(m1, m2); + + final Verifying v = new Verifying(requestManager).that(m1).that(m2); + + doNothing().when(this.requestManager).evaluateVerificationAsync( + eq(matchers), any(IsEqual.class), eq(Duration.ofMillis(1500L))); + + v.receivedTimes(42, Duration.ofMillis(1500L)); + } + + + @Test(expected=VerificationException.class) + public void receivedTimesIntWithDuration_negative() { + new Verifying(requestManager).receivedTimes(42, Duration.ofNanos(1293L)); + } + + @Test(expected = IllegalArgumentException.class) + public void receivedTimesIntNullDuration() { + new Verifying(requestManager).receivedTimes(42, null); + } @Test @SuppressWarnings("unchecked") @@ -131,6 +192,34 @@ public void receivedOnce() { public void receivedOnce_negative() { new Verifying(requestManager).receivedOnce(); } + + @Test(expected = IllegalArgumentException.class) + public void receivedOnceNullDuration() { + new Verifying(requestManager).receivedOnce(null); + } + + @Test + @SuppressWarnings("unchecked") + public void receivedOnceWithDuration() { + final Matcher m1 = mock(Matcher.class); + final Matcher m2 = mock(Matcher.class); + final Collection> matchers = Arrays.>asList(m1, m2); + + final Verifying v = new Verifying(requestManager).that(m1).that(m2); + + doNothing() + .when(this.requestManager) + .evaluateVerificationAsync( + eq(matchers), any(IsEqual.class), eq(Duration.ofSeconds(2L))); + + v.receivedOnce(Duration.ofSeconds(2L)); + } + + + @Test(expected=VerificationException.class) + public void receivedOnceWithDuration_negative() { + new Verifying(requestManager).receivedOnce(Duration.ofSeconds(2L)); + } @Test @@ -152,4 +241,4 @@ public void receivedNever() { public void receivedNever_negative() { new Verifying(requestManager).receivedNever(); } -} \ No newline at end of file +}