com.mastfrog.http.harness.HttpTestHarness Maven / Gradle / Ivy
/*
* The MIT License
*
* Copyright 2022 Tim Boudreau.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.mastfrog.http.harness;
import java.net.http.HttpRequest;
import java.nio.charset.Charset;
import java.time.Duration;
import java.util.List;
import java.util.function.Function;
/**
* A test harness for HTTP web apis - the general pattern of use is, you define
* a request to make, using one of the methods that returns a
* TestRequest
, configure it with any headers or other things
* needed, and then call its test(Consumer<Assertions>)
* method, set up your assertions within the closure of that callback, after
* which the request runs and assertions are applied. That call gives you back a
* TestResults
object, which further operations can be performed
* on. The calling thread is blocked until the response is complete; if you wish
* not to block the calling thread, use
* TestRequest.applyingAssertions()
instead.
*
* Some code being worth a thousand words:
*
*
* harness.get("http://localhost:12345/hello")
* .header("user-agent", "Uber-Whatzis-1.0")
* .responseStartTimeout(Duration.ofSeconds(10))
* .responseFinishedTimeout(Duration.ofSeconds(10))
* .test((Assertions asserts) -> {
* asserts.assertHasHeader("content-type")
* .assertHasBody()
* .assertHasHeader("wookies")
* .assertHeaderEquals("wookies", "food")
* .assertResponseCodeIn(200, 201)
* .assertBody(StringPredicates.predicate("Hello world!"));
* }).printResults();
*
*
* Since it is common for tests to start a server on a random port that cannot
* be hard-coded into a test (unless you want to prevent concurrent tests), the
* convenience method convertingToUrisWith()
allows you to, say,
* use just the path portion of a URL as a string in all of the methods that
* return a TestRequest
, supplying a function that will apply the
* protocol and host and port and convert that into a usable URI.
*
*
* Asynchronous operation: The test harness is full asynchronous, and
* does not tie up the calling thread (or any others) unless you want it to,
* making parallelizing tests trivial.
*
*
* @author Tim Boudreau
*/
public interface HttpTestHarness {
/**
* Initiate an HTTP GET request.
*
* @param uri A URI or similar object
* @return An object for configuring and launching the request
*/
TestRequest get(U uri);
/**
* Initiate an HTTP DELETE request.
*
* @param uri A URI or similar object
* @return An object for configuring and launching the request
*/
TestRequest delete(U uri);
/**
* Initiate an HTTP PUT request, with a byte-array body.
*
* @param uri A URI or similar object
* @param bytes Some bytes
* @return An object for configuring and launching the request
*/
TestRequest put(U uri, byte[] bytes);
/**
* Initiate an HTTP PUT request, with a string body which will be encoded
* using UTF-8 encoding.
*
* @param uri A URI or similar object
* @param string some text
* @return An object for configuring and launching the request
*/
TestRequest put(U uri, String string);
/**
* Initiate an HTTP PUT request, with a string body which will be encoded
* using the passed character set.
*
* @param uri A URI or similar object
* @param string some text
* @param charset A character set
* @return An object for configuring and launching the request
*/
TestRequest put(U uri, String string, Charset charset);
/**
* Initiate an HTTP PUT request, with a BodyPublisher that the HTTP client
* will call back to fetch the request body.
*
* @param uri A URI or similar object
* @param pub A body publisher from the JDK's HTTP client api
* @return An object for configuring and launching the request
*/
TestRequest put(U uri, HttpRequest.BodyPublisher pub);
/**
* Initiate an HTTP PUT request, with a request body serialized from the
* passed object using the Codec this harness was configured with when it
* was created (for example, Jackson's ObjectMapper).
*
* @param A type
* @param uri A URI or similar
* @param toSerialize An object to convert using the codec into a byte
* stream
* @return this
*/
TestRequest putObject(U uri, T toSerialize);
/**
* Initiate an HTTP POST request, with a byte-array body.
*
* @param uri A URI or similar object
* @param bytes Some bytes
* @return An object for configuring and launching the request
*/
TestRequest post(U uri, byte[] bytes);
/**
* Initiate an HTTP POST request, with a string body which will be encoded
* using UTF-8 encoding.
*
* @param uri A URI or similar object
* @param string some text
* @return An object for configuring and launching the request
*/
TestRequest post(U uri, String string);
/**
* Initiate an HTTP POST request, with a string body which will be encoded
* using the passed character set.
*
* @param uri A URI or similar object
* @param string some text
* @param charset A character set
* @return An object for configuring and launching the request
*/
TestRequest post(U uri, String string, Charset charset);
/**
* Initiate an HTTP PUT request, with a BodyPublisher that the HTTP client
* will call back to fetch the request body.
*
* @param uri A URI or similar object
* @param pub A body publisher from the JDK's HTTP client api
* @return An object for configuring and launching the request
*/
TestRequest post(U uri, HttpRequest.BodyPublisher pub);
/**
* Initiate an HTTP POST request, with a request body serialized from the
* passed object using the Codec this harness was configured with when it
* was created (for example, Jackson's ObjectMapper).
*
* @param A type
* @param uri A URI or similar
* @param toSerialize An object to convert using the codec into a byte
* stream
* @return this
*/
TestRequest postObject(U uri, T toSerialize);
/**
* Shut down this test harness, immediately aborting any requests in
* progress, and waiting for them to exit.
*
* @return this
*/
HttpTestHarness shutdown();
/**
* Await exit of all running requests. New requests may still be submitted,
* and await() can be used again - this simply waits until the count of
* running requests has dropped to zero.
*
* @param dur A timeout
* @return true if running request exited before the timeout
* @throws InterruptedException
*/
boolean await(Duration dur) throws InterruptedException;
/**
* Await exit of all running requests, for however long that takes. New
* requests may still be submitted, and await() can be used again - this
* simply waits until the count of running requests has dropped to zero.
*
* @return this
* @throws InterruptedException
*/
HttpTestHarness await() throws InterruptedException;
/**
* Initiate a new request (you will need to set the URI, HTTP method, and so
* forth on it to have a valid request - see the javadoc for the JDK's
* HttpRequestBuilder).
*
* @return A request
*/
TestRequest request();
/**
* For convenience, if the protocol or port in your test varies, use this
* function to allow your tests to pass something like a string URL path +
* query, and convert that to a URI in the function you pass here, so tests
* stay focused on business logic, not configuration details.
*
* Note that if you have many tests that are served well by this but a few
* that really need to pass a URI directly, you can always call
* request()
and set the method and URI on the resulting
* TestRequest
which remains URI-based.
*
*
* @param The type to convert from
* @param converter A conversion function
* @return A wrapper for this HTTP test harness that takes a different type
* for its uri parameters
*/
default HttpTestHarness convertingToUrisWith(Function converter) {
return new URIConvertingTestHarness<>(converter, this);
}
/**
* Get a list of all running or historically run tasks in this test harness,
* which can be used to cancel tasks or get details of their status.
*
* @return A list of tasks
*/
List extends Task> tasks();
/**
* Get the number of tasks which are currently running and have neither
* failed, completed, timed out nor been cancelled.
*
* @return A task count greater than or equal to zero
*/
int currentlyRunningTasks();
/**
* Create a builder for a new HttpTestHarness.
*
* @return A builder
*/
public static TestHarnessBuilder builder() {
return new TestHarnessBuilder();
}
public void awaitQuiet(Duration dur, boolean killOnTimeout);
}