/* * Copyright 2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package ratpack.test; import ratpack.func.Factory; import ratpack.impose.ForceDevelopmentImposition; import ratpack.impose.ForceServerListenPortImposition; import ratpack.impose.Impositions; import ratpack.impose.ImpositionsSpec; import ratpack.server.RatpackServer; import java.net.URI; import java.net.URISyntaxException; import static ratpack.util.Exceptions.uncheck; /** * An {@link ApplicationUnderTest} implementation that manages a {@link RatpackServer}. * <p> * This class can be used in tests to handle starting the server, making HTTP requests to it and shutting it down when done. * It is typically used in tests where the actual application is available on the classpath. * <p> * Implementations need only provide a {@link #createServer()} method. * <p> * Closing this application under test will {@link RatpackServer#stop() stop the server}. * Users should ensure that objects of this type are closed when done with, to release ports and other resources. * This is typically done in a test cleanup method, such as via JUnit's {@code @After} mechanism. * <p> * This class supports {@link Impositions}, which can be used to augment the server for testability. * * @see MainClassApplicationUnderTest * @see #addImpositions(ImpositionsSpec) */ public abstract class ServerBackedApplicationUnderTest implements CloseableApplicationUnderTest { private RatpackServer server; /** * Creates a new instance backed by the given server. * * @param ratpackServer the server to test * @return an application under test backed by the given server * @since 1.2 */ public static ServerBackedApplicationUnderTest of(RatpackServer ratpackServer) { return of(Factory.constant(ratpackServer)); } /** * Creates a new instance backed by the server returned by the given function. * <p> * The function is called lazily, the first time the server is needed. * * @param ratpackServer the server to test * @return an application under test backed by the given server * @since 1.2 */ public static ServerBackedApplicationUnderTest of(Factory<? extends RatpackServer> ratpackServer) { return new ServerBackedApplicationUnderTest() { @Override protected RatpackServer createServer() throws Exception { return ratpackServer.create(); } }; } /** * Creates the server to be tested. * <p> * The server does not need to be started when returned by this method. * * @return the server to test * @throws Exception any */ protected abstract RatpackServer createServer() throws Exception; /** * Creates the {@link Impositions} to impose on the server. * <p> * This implementation effectively delegates to {@link #addDefaultImpositions(ImpositionsSpec)} and {@link #addImpositions(ImpositionsSpec)}. * <p> * It is generally more appropriate to override {@link #addImpositions(ImpositionsSpec)} than this method. * * @return the impositions * @throws Exception any * @since 1.2 */ protected Impositions createImpositions() throws Exception { return Impositions.of(i -> { addDefaultImpositions(i); addImpositions(i); }); } /** * Adds default impositions, that make sense in most cases. * <p> * Specifically adds a {@link ForceDevelopmentImposition} with a {@code true} value, * and a {@link ForceServerListenPortImposition#ephemeral()} imposition. * <p> * To negate or change the default impositions, simply add a different imposition of the same type in {@link #addImpositions(ImpositionsSpec)}. * Doing so will overwrite the existing imposition of the same type, set by this method. * <p> * It is generally not necessary to override this method. * * @param impositionsSpec the impositions spec, that impositions can be added to * @since 1.2 */ protected void addDefaultImpositions(ImpositionsSpec impositionsSpec) { impositionsSpec.add(ForceServerListenPortImposition.ephemeral()); impositionsSpec.add(ForceDevelopmentImposition.of(true)); } /** * Adds impositions to be imposed on the server while it is being created and starting. * * <pre class="java">{@code * import ratpack.server.RatpackServer; * import ratpack.impose.ImpositionsSpec; * import ratpack.impose.ServerConfigImposition; * import ratpack.test.MainClassApplicationUnderTest; * * import static java.util.Collections.singletonMap; * import static org.junit.Assert.assertEquals; * * public class Example { * * public static class App { * public static void main(String[] args) throws Exception { * RatpackServer.start(s -> s * .serverConfig(c -> c * .props(singletonMap("string", "foo")) * .require("/string", String.class) * ) * .handlers(c -> c * .get(ctx -> ctx.render(ctx.get(String.class))) * ) * ); * } * } * * public static void main(String[] args) throws Exception { * new MainClassApplicationUnderTest(App.class) { * {@literal @}Override * protected void addImpositions(ImpositionsSpec impositions) { * impositions.add(ServerConfigImposition.of(c -> * c.props(singletonMap("string", "bar")) * )); * } * }.test(testHttpClient -> * assertEquals("bar", testHttpClient.getText()) * ); * } * } * }</pre> * * @param impositions the spec to add impositions to * @see Impositions * @see ratpack.impose.ServerConfigImposition * @see ratpack.impose.ForceDevelopmentImposition * @see ratpack.impose.ForceServerListenPortImposition * @see ratpack.impose.UserRegistryImposition * @since 1.2 */ protected void addImpositions(ImpositionsSpec impositions) { } /** * Returns the address to the root of the server, starting it if necessary. * * @return the address to the root of the server */ @Override public URI getAddress() { if (server == null) { try { server = createImpositions().impose(() -> { RatpackServer server = createServer(); server.start(); return server; }); } catch (Exception e) { throw uncheck(e); } } URI address; try { address = new URI(server.getScheme() + "://" + server.getBindHost() + ":" + server.getBindPort() + "/"); } catch (URISyntaxException e) { throw uncheck(e); } return address; } /** * Stops the server if it is running. * * @see RatpackServer#stop() */ public void stop() { if (server != null) { try { server.stop(); server = null; } catch (Exception e) { throw uncheck(e); } } } /** * Delegates to {@link #stop()}. */ public void close() { stop(); } }