Skip to content

Add ExpectedCount support to MockWebServiceServer - #1945

Open
sharanggupta wants to merge 1 commit into
spring-projects:mainfrom
sharanggupta:gh-1154-expected-count
Open

sharanggupta wants to merge 1 commit into
spring-projects:mainfrom
sharanggupta:gh-1154-expected-count

Conversation

@sharanggupta

Copy link
Copy Markdown

This adds ExpectedCount support to the MockWebServiceServer, as discussed in #1154, harmonized with the ExpectedCount of the MockRestServiceServer in Spring Framework.

mockServer.expect(ExpectedCount.times(3), payload(requestPayload)).andRespond(withPayload(responsePayload));

What it adds

  • ExpectedCount with the same factory methods and ranges as Spring Framework: once(), twice(), times(int), min(int), max(int), between(int, int), manyTimes() and never().
  • MockWebServiceServer.expect(ExpectedCount, RequestMatcher). expect(RequestMatcher) is now a shortcut for expect(ExpectedCount.once(), requestMatcher).
  • The expectations are still met in the order in which they are recorded, with the semantics of the SimpleRequestExpectationManager of Spring Framework: a request is matched against the expectations that already matched a request and can match more first, then against the next expectation. verify() fails when an expectation was met fewer times than its minimum count.
  • A "Expecting a Request More Than Once" section in the client-side testing chapter.

Internals

The package-private MockSenderConnection used to be both the expectation and the connection, and cleared its matchers on close(), so it could not serve more than one request. It is now split into a package-private RequestExpectation (matchers, response creator and count, implementing ResponseActions) and a MockSenderConnection per request, which finds its expectation in send(). The MockWebServiceMessageSender now synchronizes access to its expectations, since matching a request updates more than one piece of state.

The existing messages are kept: "No further connections expected" for an extra connection, "Further connection(s) expected" from verify(), and the IllegalStateException when an expectation is recorded after the test started. When a request does not meet an expectation that can be met more than once and no further expectation is left, the mismatch of that expectation is reported (for instance the payload difference), as it is for the first request.

One behavior change for existing tests: an expectation now counts only the requests that met it. If a test sends a request that does not match, catches the AssertionError, and then calls verify(), verify() now fails, because the expectation was not met. Before, the request consumed the expectation and verify() passed. This is also how MockRestServiceServer.verify() behaves after a failed request. verifyFailsAfterARequestThatDidNotMeetTheExpectation pins it, and I can restore the previous behavior if you prefer.

A related fix

Once an expectation can be met more than once, its matchers and response creator run more than once. The PayloadDiffMatcher, SoapEnvelopeDiffMatcher, PayloadMessageCreator and SoapEnvelopeMessageCreator transformed their Source on every call, but the ResourceSource used by the Resource variants of payload(), soapEnvelope(), withPayload() and withSoapEnvelope() wraps an input stream that can be read only once. They now transform the source once and reuse the document; the creators still copy it into each message. Three tests cover this and failed before that part of the change.

I did not add an unordered mode (Spring Framework's ignoreExpectOrder). It would build on the same RequestExpectation, and I can follow up if you want it.

Testing

Developed test first: the new tests do not compile against main (ExpectedCount and the new expect variant), and the Resource tests failed with the counting in place until the sources were read once.

  • ExpectedCountTests for the ranges and argument checks.
  • MockWebServiceServerTests through a WebServiceTemplate: times, twice, min, max, between, never with and without a request, too many and too few requests, the order of the expectations, a range that lets the next expectation match, a request that matches no expectation, verify() after a mismatched request, reset(), and repeated Resource payloads and SOAP envelopes.
  • MockSenderConnectionTests and MockWebServiceMessageSenderTests adapted to the split.
  • ./gradlew :spring-ws-test:check :spring-ws-test:javadoc :spring-ws-docs:asciidoctor passes locally on JDK 25 (112 tests in spring-ws-test, none skipped).

MockWebServiceServer expects every request exactly once, so a client
that sends the same request several times needs as many identical
expectations, and a request that is sent a variable number of times
cannot be expected at all.

This commit adds ExpectedCount, modeled after the one of the
MockRestServiceServer in Spring Framework, and an
expect(ExpectedCount, RequestMatcher) variant. The expectations are
still met in the order in which they are recorded, like the
SimpleRequestExpectationManager of Spring Framework does: a request is
matched against the expectations that already matched a request and
can match more first, then against the next expectation. verify() fails
if an expectation was met fewer times than its minimum count.

As an expectation can now be met more than once, the payload and SOAP
envelope matchers and creators transform their Source only once. A
ResourceSource can be read only once, which broke the Resource variants
of payload(), soapEnvelope(), withPayload() and withSoapEnvelope() on
the second request.

See spring-projectsgh-1154

Signed-off-by: Sharang Gupta <sharang@sharanggupta.dev>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status: waiting-for-triage An issue we've not yet triaged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants