-
-
Save kimble/2789987 to your computer and use it in GitHub Desktop.
| package com.developerb.dropwizard; | |
| import com.yammer.dropwizard.AbstractService; | |
| import com.yammer.dropwizard.Service; | |
| import com.yammer.dropwizard.cli.Command; | |
| import com.yammer.dropwizard.config.Configuration; | |
| import org.junit.rules.TestRule; | |
| import org.junit.runner.Description; | |
| import org.junit.runners.model.Statement; | |
| import java.lang.reflect.Method; | |
| import java.net.URI; | |
| /** | |
| * JUnit @Rule that'll start and stop a Dropwizard service around each test method. | |
| * | |
| * This class might be extended with factory methods for pre-configured http client | |
| * instance for both the main and the internal service endpoint. | |
| * | |
| * @author Kim A. Betti <[email protected]> | |
| */ | |
| public class DropwizardTestServer<C extends Configuration, S extends Service<C>> implements TestRule { | |
| private final Class<C> configurationClass; | |
| private final Class<S> serviceClass; | |
| private final String config; | |
| private TestableServerCommand<C> command; | |
| private S service; | |
| protected DropwizardTestServer(Class<C> configClass, Class<S> serviceClass, String config) { | |
| this.configurationClass = configClass; | |
| this.serviceClass = serviceClass; | |
| this.config = config; | |
| } | |
| public static <C extends Configuration, S extends Service<C>> DropwizardTestServer<C, S> testServer( | |
| Class<C> configClass, Class<S> serviceClass, String config) { | |
| return new DropwizardTestServer<>(configClass, serviceClass, config); | |
| } | |
| @Override | |
| public Statement apply(Statement base, Description description) { | |
| return new DropwizardStatement(base); | |
| } | |
| public boolean isRunning() { | |
| return command.isRunning(); | |
| } | |
| public S getService() { | |
| return service; | |
| } | |
| public URI getPublicRootUri() { | |
| return command.getRootUriForConnector("main"); | |
| } | |
| public URI getInternalRootUri() { | |
| return command.getRootUriForConnector("internal"); | |
| } | |
| private class DropwizardStatement extends Statement { | |
| private final Statement base; | |
| public DropwizardStatement(Statement base) { | |
| this.base = base; | |
| } | |
| @Override | |
| public void evaluate() throws Throwable { | |
| service = serviceClass.newInstance(); | |
| registerTestCommand(service); | |
| try { | |
| service.run(new String[] { "test-server", config }); | |
| base.evaluate(); | |
| } | |
| finally { | |
| command.stop(); | |
| } | |
| } | |
| /** | |
| * Register a custom command that'll allow us to register our test-server | |
| * startup logic that in turn will let us shut it down in a controlled fashion. | |
| * | |
| * I really don't like using reflection like this, but it's better then introducing | |
| * a new abstract class in the Service class hierarchy solely for testing purposes. | |
| */ | |
| private void registerTestCommand(Service<C> service) throws Exception { | |
| command = new TestableServerCommand<>(configurationClass); | |
| Method method = AbstractService.class.getDeclaredMethod("addCommand", Command.class); | |
| method.setAccessible(true); | |
| method.invoke(service, command); | |
| } | |
| } | |
| } |
| package com.developerb.dropwizard; | |
| import com.developerb.dropwizard.DropwizardTestServer; | |
| import com.google.inject.Injector; | |
| import Sample.server.SampleConfiguration; | |
| import Sample.server.SampleService; | |
| import org.apache.http.client.HttpClient; | |
| import org.apache.http.client.ResponseHandler; | |
| import org.apache.http.client.methods.HttpGet; | |
| import org.apache.http.impl.client.BasicResponseHandler; | |
| import org.junit.Rule; | |
| import org.junit.Test; | |
| import java.io.IOException; | |
| import java.net.URI; | |
| import java.net.URISyntaxException; | |
| import static com.developerb.dropwizard.DropwizardTestServer.testServer; | |
| import static junit.framework.Assert.assertEquals; | |
| import static junit.framework.Assert.assertTrue; | |
| /** | |
| * @author Kim A. Betti <[email protected]> | |
| */ | |
| public class SampleIntegrationTest { | |
| @Rule | |
| public DropwizardTestServer<SampleConfiguration, SampleService> testServer | |
| = testServer(SampleConfiguration.class, SampleService.class, "config.yml"); | |
| @Test | |
| public void shouldBeRunning() { | |
| assertTrue("Server should be running", testServer.isRunning()); | |
| } | |
| @Test | |
| public void weHaveAccessToPublicRootUri() throws URISyntaxException { | |
| URI expectedUri = new URI("http://localhost:8080"); | |
| assertEquals(expectedUri, testServer.getPublicRootUri()); | |
| } | |
| @Test | |
| public void weAlsoHaveAccessToInternalRootUri() throws URISyntaxException { | |
| URI expectedUri = new URI("http://localhost:8081"); | |
| assertEquals(expectedUri, testServer.getInternalRootUri()); | |
| } | |
| @Test | |
| public void playingPingPong() throws IOException { | |
| HttpClient httpClient = getHttpClientFromService(); | |
| URI root = testServer.getInternalRootUri(); | |
| String pingResponseBody = executeSimpleHttpGet(httpClient, root, "/ping"); | |
| assertEquals("pong", pingResponseBody.trim()); | |
| httpClient.getConnectionManager().shutdown(); | |
| } | |
| private String executeSimpleHttpGet(HttpClient httpClient, URI root, String path) throws IOException { | |
| HttpGet initialFetch = new HttpGet(root.toString() + path); | |
| ResponseHandler<String> responseHandler = new BasicResponseHandler(); | |
| return httpClient.execute(initialFetch, responseHandler); | |
| } | |
| private HttpClient getHttpClientFromService() { | |
| SampleService service = testServer.getService(); | |
| Injector injector = service.getInjector(); | |
| return injector.getInstance(HttpClient.class); | |
| } | |
| } |
| package com.developerb.dropwizard; | |
| import com.yammer.dropwizard.AbstractService; | |
| import com.yammer.dropwizard.cli.ConfiguredCommand; | |
| import com.yammer.dropwizard.config.Configuration; | |
| import com.yammer.dropwizard.config.Environment; | |
| import com.yammer.dropwizard.config.HttpConfiguration; | |
| import com.yammer.dropwizard.config.ServerFactory; | |
| import com.yammer.dropwizard.logging.Log; | |
| import org.apache.commons.cli.CommandLine; | |
| import org.eclipse.jetty.server.Connector; | |
| import org.eclipse.jetty.server.Server; | |
| import javax.management.*; | |
| import java.lang.management.ManagementFactory; | |
| import java.net.URI; | |
| import java.net.URISyntaxException; | |
| import static com.google.common.base.Preconditions.checkArgument; | |
| /** | |
| * Normally ServerCommand is in charge of starting the service, but that's not particularly | |
| * well suited for integration testing as it joins the current thread and keeps the Server | |
| * instance to itself. | |
| * | |
| * This implementation is based on the original ServerCommand, but in addition to being | |
| * stoppable it provides a few convenience methods for tests. | |
| * | |
| * @author Kim A. Betti <[email protected]> | |
| */ | |
| public class TestableServerCommand<T extends Configuration> extends ConfiguredCommand<T> { | |
| private final Log log = Log.forClass(TestableServerCommand.class); | |
| private final Class<T> configurationClass; | |
| private Server server; | |
| public TestableServerCommand(Class<T> configurationClass) { | |
| super("test-server", "Starts an HTTP test-server running the service"); | |
| this.configurationClass = configurationClass; | |
| } | |
| @Override | |
| protected Class<T> getConfigurationClass() { | |
| return configurationClass; | |
| } | |
| @Override | |
| protected void run(AbstractService<T> service, T configuration, CommandLine params) throws Exception { | |
| server = initializeServer(service, configuration); | |
| try { | |
| server.start(); | |
| } | |
| catch (Exception e) { | |
| log.error(e, "Unable to start test-server, shutting down"); | |
| server.stop(); | |
| } | |
| } | |
| public void stop() throws Exception { | |
| try { | |
| stopJetty(); | |
| } | |
| finally { | |
| unRegisterLoggingMBean(); | |
| } | |
| } | |
| /** | |
| * We won't be able to run more then a single test in the same JVM instance unless | |
| * we do some tidying and un-register a logging m-bean added by Dropwizard. | |
| */ | |
| private void unRegisterLoggingMBean() throws Exception { | |
| MBeanServer server = ManagementFactory.getPlatformMBeanServer(); | |
| ObjectName loggerObjectName = new ObjectName("com.yammer:type=Logging"); | |
| if (server.isRegistered(loggerObjectName)) { | |
| server.unregisterMBean(loggerObjectName); | |
| } | |
| } | |
| private void stopJetty() throws Exception { | |
| if (server != null) { | |
| server.stop(); | |
| checkArgument(server.isStopped()); | |
| } | |
| } | |
| public boolean isRunning() { | |
| return server.isRunning(); | |
| } | |
| public URI getRootUriForConnector(String connectorName) { | |
| try { | |
| Connector connector = getConnectorNamed(connectorName); | |
| String host = connector.getHost() != null ? connector.getHost() : "localhost"; | |
| return new URI("http://" + host + ":" + connector.getPort()); | |
| } | |
| catch (URISyntaxException e) { | |
| throw new IllegalStateException(e); | |
| } | |
| } | |
| private Connector getConnectorNamed(String name) { | |
| Connector[] connectors = server.getConnectors(); | |
| for (Connector connector : connectors) { | |
| if (connector.getName().equals(name)) { | |
| return connector; | |
| } | |
| } | |
| throw new IllegalStateException("No connector named " + name); | |
| } | |
| private Server initializeServer(AbstractService<T> service, T configuration) throws Exception { | |
| Environment environment = getInitializedEnvironment(service, configuration); | |
| ServerFactory serverFactory = getServerFactory(service, configuration); | |
| return serverFactory.buildServer(environment); | |
| } | |
| private ServerFactory getServerFactory(AbstractService<T> service, T configuration) { | |
| HttpConfiguration httpConfig = configuration.getHttpConfiguration(); | |
| return new ServerFactory(httpConfig, service.getName()); | |
| } | |
| private Environment getInitializedEnvironment(AbstractService<T> service, T configuration) throws Exception { | |
| Environment environment = new Environment(configuration, service); | |
| service.initializeWithBundles(configuration, environment); | |
| return environment; | |
| } | |
| } |
thanks kimble for the work on this. It's proven very nice for a way to integration test DAO's. I see in your SampleIntegrationTest.playingPingPong() test is looks like you implemented Guice w/in DropWizard...
private HttpClient getHttpClientFromService() {
SampleService service = testServer.getService();
Injector injector = service.getInjector(); <<<<<<<<<<<<<<<<<<<<<<<
return injector.getInstance(HttpClient.class); <<<<<<<<<<<<<<<<<<
}
Did you follow this group thread on your implementation of guice into dropwizard? - https://groups.google.com/forum/#!topic/dropwizard-user/LHCSUT960AE
I also noticed your gist around a Timer annotation using guice, nice - https://gist.github.com/2623833
thanks again for sharing your junit @rule work.
Oh, I didn't realize that people had been commenting on this! Thanks for all the feedback!
Thanks for sharing this. If folks can settle for a class-level JUnit approach for integration testing, Dropwizard 0.6.2 seems to have quietly added a DropwizardServiceRule (see https://github.com/codahale/dropwizard/pull/307/files) for this purpose.
import com.yammer.dropwizard.testing.junit.DropwizardServiceRule;
...
@ClassRule
public static final DropwizardServiceRule<TestConfiguration> RULE =
new DropwizardServiceRule<TestConfiguration>(MyService.class,
Resources.getResource("my-service-config.yml").getPath());
...
@Test
public void fooTest() {
Client client = new Client();
String root = String.format("http://localhost:%d/", RULE.getLocalPort());
URI uri = UriBuilder.fromUri(root).path("/foo").build();
WebResource rs = client.resource(uri);
...
API (without docs, unfortunately) appears to be here too:
http://dropwizard.codahale.com/maven/dropwizard-testing/apidocs/com/yammer/dropwizard/testing/junit/DropwizardServiceRule.html
Dropwizard 0.6.0-SNAPSHOT is fixed regarding the async logger issue. However the rule needs major changes.. I will update the rule when dropwizard 0.6.0 is released, as I got it working after some more hacks