Skip to content

Commit

Permalink
add ability to disable managed objects lifecycle for lightweight gui…
Browse files Browse the repository at this point in the history
…cey tests

    (start/stop methods on managed objects not called; might be useful for tests with mocks):
    * new GuiceyTestSupport().disableManagedLifecycle()
    * @TestGuiceyApp(.., managedLifecycle = false)
    * TestGuiceyAppExtension.forApp(..).disableManagedLifecycle()
    * TestSupport.build(App.class).runCoreWithoutManaged(..)
  • Loading branch information
xvik committed Feb 22, 2025
1 parent 39e59c7 commit f29b2cf
Show file tree
Hide file tree
Showing 10 changed files with 211 additions and 5 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@
- Add test lifecycle listeners: could be registered with TestEnvironmentSetup (listen() method or lambda-based on* methods)
and provide notifications for guicey extension lifecycle (app start/stop, before/after test).
This is a simple alternative to writing junit extensions for an additional integrations (db, testcontainers etc.).
- Add ability to disable managed objects lifecycle for lightweight guicey tests
(start/stop methods on managed objects not called; might be useful for tests with mocks):
* new GuiceyTestSupport().disableManagedLifecycle()
* @TestGuiceyApp(.., managedLifecycle = false)
* TestGuiceyAppExtension.forApp(..).disableManagedLifecycle()
* TestSupport.build(App.class).runCoreWithoutManaged(..)
- Add annotated fields search api in test class for setup objects (TestEnvironmentSetup): findFields(..)
(to simplify writing annotation-driven extensions).
- If setup object (TestEnvironmentSetup) implements hook and/or listener interface, it would be
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,16 @@ public GuiceyTestSupport(final Class<? extends Application<C>> applicationClass,
super(applicationClass, configuration, new CmdProvider<>());
}

/**
* By default, guicey simulates {@link io.dropwizard.lifecycle.Managed} (and
* {@link org.eclipse.jetty.util.component.LifeCycle}) lifecycles. It might be not required for tests with
* mocks.
*/
public GuiceyTestSupport<C> disableManagedLifecycle() {
((CmdProvider<C>) this.commandInstantiator).disableManagedSimulation();
return this;
}

/**
* Normally, {@link #before()} and {@link #after()} methods are called separately. This method is a shortcut
* mostly for errors testing when {@link #before()} assumed to fail to make sure {@link #after()} will be called
Expand Down Expand Up @@ -108,12 +118,18 @@ public void after() {
@SuppressWarnings("checkstyle:VisibilityModifier")
static class CmdProvider<C extends Configuration> implements Function<Application<C>, Command> {

private boolean simulateManaged = true;
public TestCommand<C> command;

public void disableManagedSimulation() {
Preconditions.checkState(command == null, "Command already initialized");
simulateManaged = false;
}

@Override
public Command apply(final Application<C> application) {
Preconditions.checkState(command == null, "Command already created");
command = new TestCommand<>(application);
command = new TestCommand<>(application, simulateManaged);
return command;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import io.dropwizard.core.setup.Environment;
import net.sourceforge.argparse4j.inf.Namespace;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Lightweight variation of server command for testing purposes.
Expand All @@ -17,22 +19,33 @@
*/
public class TestCommand<C extends Configuration> extends EnvironmentCommand<C> {

private final Logger logger = LoggerFactory.getLogger(TestCommand.class);
private final Class<C> configurationClass;
private final boolean simulateManaged;
private ContainerLifeCycle container;

public TestCommand(final Application<C> application) {
this(application, true);
}

public TestCommand(final Application<C> application, final boolean simulateManaged) {
super(application, "guicey-test", "Specific command to run guice context without jetty server");
cleanupAsynchronously();
configurationClass = application.getConfigurationClass();
this.simulateManaged = simulateManaged;
}

@Override
protected void run(final Environment environment, final Namespace namespace,
final C configuration) throws Exception {
// simulating managed objects lifecycle support
container = new ContainerLifeCycle();
environment.lifecycle().attach(container);
container.start();
if (simulateManaged) {
container = new ContainerLifeCycle();
environment.lifecycle().attach(container);
container.start();
} else {
logger.info("NOTE: Managed lifecycle support disabled!");
}
}

public void stop() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,25 @@ public <T> T runCore(final @Nullable TestSupport.RunCallback<T> action) throws E
return run(buildCoreInternal(), action);
}

/**
* Start and stop application without web services. Provided action would be executed in time of application life.
* Does not simulate {@link io.dropwizard.lifecycle.Managed} objects lifecycle (start/stop would not be called).
* <p>
* NOTE: method not supposed to be used for multiple calls. For example, registered hooks would only work
* on first execution.
*
* @param action action to execute while the application is running
* @param <T> result type
* @return action result
* @throws Exception any appeared exception (throws may easily be added directly to test method and, without
* extra exception wrapper, we get exact exceptions as they would be thrown in real application)
*/
public <T> T runCoreWithoutManaged(final @Nullable TestSupport.RunCallback<T> action) throws Exception {
return run(buildCoreInternal().disableManagedLifecycle(), action);
}



/**
* Start and stop application with web services. Mostly useful to test application startup errors
* (with proper application shutdown).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,4 +192,13 @@
* @return client factory class
*/
Class<? extends TestClientFactory> clientFactory() default DefaultTestClientFactory.class;

/**
* By default, guicey simulates {@link io.dropwizard.lifecycle.Managed} (and
* {@link org.eclipse.jetty.util.component.LifeCycle}) lifecycles. It might be not required for tests with
* mocks.
*
* @return true to simulate managed objects lifecycle, false to disable simulation
*/
boolean managedLifecycle() default true;
}
Original file line number Diff line number Diff line change
Expand Up @@ -152,10 +152,14 @@ private <C extends Configuration> DropwizardTestSupport<C> create(
final String configPath,
final ExtensionContext context) {
// NOTE: DropwizardTestSupport.ServiceListener listeners would be called ONLY on start!
return new GuiceyTestSupport<C>((Class<? extends Application<C>>) app,
final GuiceyTestSupport support = new GuiceyTestSupport<C>((Class<? extends Application<C>>) app,
configPath,
configPrefix,
buildConfigOverrides(configPrefix, context));
if (!config.managedLifecycle) {
support.disableManagedLifecycle();
}
return support;
}

@SuppressWarnings("unchecked")
Expand Down Expand Up @@ -237,6 +241,19 @@ public Builder setup(final TestEnvironmentSetup... support) {
return this;
}

/**
* By default, guicey simulates {@link io.dropwizard.lifecycle.Managed} (and
* {@link org.eclipse.jetty.util.component.LifeCycle}) lifecycles. It might be not required for tests with
* mocks.
*
* @return true to simulate managed objects lifecycle
* @return builder instance for chained calls
*/
public Builder disableManagedLifecycle() {
cfg.managedLifecycle = false;
return this;
}

/**
* Creates extension.
* <p>
Expand All @@ -257,6 +274,7 @@ public TestGuiceyAppExtension create() {
private static class Config extends ExtensionConfig {
Class<? extends Application> app;
String configPath = "";
boolean managedLifecycle = true;

Config() {
super(new TestExtensionsTracker());
Expand Down Expand Up @@ -295,6 +313,7 @@ static Config parse(final TestGuiceyApp ann, final TestExtensionsTracker tracker
res.reuseApp = ann.reuseApplication();
res.defaultExtensionsEnabled = ann.useDefaultExtensions();
res.clientFactory(ann.clientFactory());
res.managedLifecycle = ann.managedLifecycle();
return res;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package ru.vyarus.dropwizard.guice.test.general;

import io.dropwizard.lifecycle.Managed;
import jakarta.inject.Singleton;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import ru.vyarus.dropwizard.guice.GuiceBundle;
import ru.vyarus.dropwizard.guice.support.DefaultTestApp;
import ru.vyarus.dropwizard.guice.test.TestSupport;

/**
* @author Vyacheslav Rusakov
* @since 22.02.2025
*/
public class BuilderRunCoreWithoutManaged {

@Test
void testCoreRun() throws Exception {

UnusedManaged managed = TestSupport.build(App.class)
.runCore(injector -> injector.getInstance(UnusedManaged.class));

Assertions.assertTrue(managed.started);
Assertions.assertTrue(managed.stopped);
}

@Test
void testCoreRunWithoutManagedLifecycle() throws Exception {

UnusedManaged managed = TestSupport.build(App.class)
.runCoreWithoutManaged(injector -> injector.getInstance(UnusedManaged.class));

Assertions.assertFalse(managed.started);
Assertions.assertFalse(managed.stopped);
}

public static class App extends DefaultTestApp {
@Override
protected GuiceBundle configure() {
return GuiceBundle.builder()
.extensions(UnusedManaged.class)
.build();
}
}

@Singleton
public static class UnusedManaged implements Managed {
public boolean started = false;
public boolean stopped = false;

@Override
public void start() throws Exception {
started = true;
}

@Override
public void stop() throws Exception {
stopped = true;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.google.inject.Key;
import io.dropwizard.configuration.FileConfigurationSourceProvider;
import io.dropwizard.configuration.SubstitutingSourceProvider;
import io.dropwizard.core.Configuration;
import org.apache.commons.text.StringSubstitutor;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -41,4 +42,15 @@ void testRunWithinStartedSupport() throws Exception {
return null;
});
}

@Test
void testRunWithoutManagedLifecycle() throws Exception {
GuiceyTestSupport<Configuration> support = new GuiceyTestSupport<>(BuilderRunCoreWithoutManaged.App.class, (String) null)
.disableManagedLifecycle();

BuilderRunCoreWithoutManaged.UnusedManaged managed = support
.run(injector -> injector.getInstance(BuilderRunCoreWithoutManaged.UnusedManaged.class));
Assertions.assertThat(managed.started).isFalse();
Assertions.assertThat(managed.stopped).isFalse();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package ru.vyarus.dropwizard.guice.test.jupiter.guicey;

import com.google.inject.Inject;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import ru.vyarus.dropwizard.guice.test.general.BuilderRunCoreWithoutManaged;
import ru.vyarus.dropwizard.guice.test.jupiter.ext.TestGuiceyAppExtension;

/**
* @author Vyacheslav Rusakov
* @since 22.02.2025
*/
public class NoManagedLifecycleManualTest {

@RegisterExtension
static TestGuiceyAppExtension ext = TestGuiceyAppExtension.forApp(BuilderRunCoreWithoutManaged.App.class)
.disableManagedLifecycle()
.create();

@Inject
BuilderRunCoreWithoutManaged.UnusedManaged managed;

@Test
void testManagedNotStarted() {
Assertions.assertFalse(managed.started);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package ru.vyarus.dropwizard.guice.test.jupiter.guicey;

import com.google.inject.Inject;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import ru.vyarus.dropwizard.guice.test.general.BuilderRunCoreWithoutManaged;
import ru.vyarus.dropwizard.guice.test.jupiter.TestGuiceyApp;

/**
* @author Vyacheslav Rusakov
* @since 22.02.2025
*/
@TestGuiceyApp(value = BuilderRunCoreWithoutManaged.App.class, managedLifecycle = false)
public class NoManagedLifecycleTest {

@Inject
BuilderRunCoreWithoutManaged.UnusedManaged managed;

@Test
void testManagedNotStarted() {
Assertions.assertFalse(managed.started);
}
}

0 comments on commit f29b2cf

Please # to comment.