Created
January 9, 2024 17:21
-
-
Save dehidehidehi/bdd2b35c2124991df8becb16245eb038 to your computer and use it in GitHub Desktop.
TestContainers class setup
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import lombok.NoArgsConstructor; | |
import lombok.extern.slf4j.Slf4j; | |
import org.jetbrains.annotations.NotNull; | |
import org.springframework.boot.test.util.TestPropertyValues; | |
import org.springframework.context.ConfigurableApplicationContext; | |
import org.springframework.test.context.ActiveProfiles; | |
import org.springframework.test.context.ContextConfiguration; | |
import org.testcontainers.containers.GenericContainer; | |
import org.testcontainers.utility.DockerImageName; | |
import java.lang.annotation.ElementType; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.RetentionPolicy; | |
import java.lang.annotation.Target; | |
import java.util.Map; | |
import java.util.Properties; | |
import static lombok.AccessLevel.PRIVATE; | |
@Slf4j | |
@NoArgsConstructor(access = PRIVATE) | |
public class SimpleTestContainer extends TestContainerBase { | |
private static final Properties TEST_PROPERTIES = readResource("application-test.properties"); | |
private static final int containerPort = 9190; | |
public static final String VAULT_ROUTES_BASE = "/"; | |
@Override | |
public GenericContainer<?> tcBuild() { | |
final var envVariables = Map.of( | |
"SPRING_PROFILES_ACTIVE", StandardProfiles.TEST, | |
"SERVER_PORT", String.valueOf(containerPort) | |
); | |
return containerBuilder(getVaultImageName(), containerPort) | |
.withEnv(envVariables); | |
} | |
@NotNull | |
private static DockerImageName getVaultImageName() { | |
final var registry = TEST_PROPERTIES.getProperty("test-containers.registry"); | |
final var documentVaultDockerImageName = TEST_PROPERTIES.getProperty("test-containers.tag.vault"); | |
final var fullImageReference = registry + "/" + vaultDockerImageName; | |
return DockerImageName.parse(fullImageReference); | |
} | |
public static class SimpleContainerInitializer implements TestContainer.Initializer { | |
@Override | |
public void initialize(final ConfigurableApplicationContext ctx) { | |
final GenericContainer<?> container = new ContainerTestContainer().tcBuild(); | |
container.start(); | |
dynamicChangeContainerServiceProperty(container, ctx); | |
} | |
private void dynamicChangeContainerServiceProperty( | |
GenericContainer<?> container, | |
final ConfigurableApplicationContext ctx) { | |
final var serviceUrl = String.format("http://%s:%d%s", | |
container.getHost(), | |
container.getFirstMappedPort(), | |
ROUTES_BASE | |
); | |
log.error("Simple Container Service URL : " + serviceUrl); | |
TestPropertyValues.of( | |
String.format("service.container.url=%s", serviceUrl)).applyTo(ctx.getEnvironment()); | |
} | |
} | |
@Target({ElementType.TYPE}) | |
@Retention(RetentionPolicy.RUNTIME) | |
@ActiveProfiles(StandardProfiles.TEST) | |
@ContextConfiguration(initializers = SimpleContainerInitializer.class) | |
public @interface EnableSimplecontainerContainer { | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import com.github.dockerjava.api.DockerClient; | |
import com.github.dockerjava.api.model.Statistics; | |
import lombok.AccessLevel; | |
import lombok.Getter; | |
import lombok.RequiredArgsConstructor; | |
import lombok.extern.slf4j.Slf4j; | |
import org.springframework.context.ApplicationContextInitializer; | |
import org.springframework.context.ConfigurableApplicationContext; | |
import org.testcontainers.containers.GenericContainer; | |
import org.testcontainers.containers.output.Slf4jLogConsumer; | |
import org.testcontainers.containers.wait.strategy.AbstractWaitStrategy; | |
import org.testcontainers.containers.wait.strategy.HttpWaitStrategy; | |
import org.testcontainers.containers.wait.strategy.WaitStrategy; | |
import org.testcontainers.shaded.com.github.dockerjava.core.InvocationBuilder.AsyncResultCallback; | |
import org.testcontainers.utility.DockerImageName; | |
import java.io.IOException; | |
import java.time.Duration; | |
import java.util.Arrays; | |
import java.util.List; | |
import java.util.Properties; | |
import java.util.concurrent.Future; | |
import java.util.stream.Collectors; | |
public interface TestContainer { | |
GenericContainer<?> tcBuild(); | |
@Slf4j | |
@Getter(AccessLevel.PROTECTED) | |
public static abstract class TestContainerBase implements TestContainer { | |
private final Slf4jLogConsumer logConsumer = new Slf4jLogConsumer(log); | |
private final WaitStrategy actuatorWaitStrategy = new HttpWaitStrategy() | |
.forPath("/actuator/health") | |
.forStatusCode(200); | |
protected GenericContainer<?> containerBuilder(DockerImageName dockerImage, int port) { | |
return containerBuilder(new GenericContainer<>(dockerImage), port); | |
} | |
protected GenericContainer<?> containerBuilder(Future<String> dockerImage, int port) { | |
return containerBuilder(new GenericContainer<>(dockerImage), port); | |
} | |
private GenericContainer<?> containerBuilder(GenericContainer<?> container, int port) { | |
container | |
.withExposedPorts(port) | |
.withLogConsumer(getLogConsumer()) | |
.waitingFor(getActuatorWaitStrategy()) | |
.withStartupTimeout(Duration.ofSeconds(30)); | |
container.setPortBindings(List.of(String.format("%d:%d", port, port))); | |
return container; | |
} | |
protected List<String> generatePortBindings(List<Pair<Integer, Integer>> bindingPairs) { | |
return bindingPairs.stream() | |
.map(p -> String.format("%d:%d", p.getFirstValue(), p.getSecondValue())) | |
.collect(Collectors.toList()); | |
} | |
/** | |
* Allows loading of properties file in a static environment. | |
*/ | |
public static Properties readResource(String path) throws Rethrowable { | |
try { | |
final var properties = new Properties(); | |
properties.load(TestContainer.class.getResourceAsStream("/" + path)); | |
return properties; | |
} catch (final IOException e) { | |
throw new Rethrowable(e); | |
} | |
} | |
} | |
/** | |
* Just waits a set amount of time. | |
*/ | |
@RequiredArgsConstructor | |
public static class SimpleWaitStrategy extends AbstractWaitStrategy { | |
private final Duration waitDuration; | |
@Override | |
protected void waitUntilReady() { | |
try { | |
Thread.sleep(waitDuration.toMillis()); | |
} catch (InterruptedException e) { | |
throw new RuntimeException(e); | |
} | |
} | |
} | |
public static interface Initializer extends ApplicationContextInitializer<ConfigurableApplicationContext> { | |
default List<String> getActiveProfiles(final ConfigurableApplicationContext ctx) { | |
return Arrays.stream(ctx.getEnvironment().getActiveProfiles()) | |
.collect(Collectors.toList()); | |
} | |
} | |
@RequiredArgsConstructor | |
public static class Stats { | |
private final DockerClient client; | |
private final String containerId; | |
protected Statistics execStats() { | |
AsyncResultCallback<Statistics> callback = new AsyncResultCallback<>(); | |
client.statsCmd(this.containerId).exec(callback); | |
return callback.awaitResult(); | |
} | |
public double currentRamUsage() { | |
return currentRamUsage(ByteUnit.MEGABYTES); | |
} | |
public double currentRamUsage(ByteUnit unit) { | |
final var memoryBytes = (double) execStats().getMemoryStats().getUsage(); | |
return switch (unit) { | |
case BYTES -> memoryBytes; | |
case KILOBYTES -> memoryBytes / 1024; | |
case MEGABYTES -> memoryBytes / 1024 / 1024; | |
}; | |
} | |
public static enum ByteUnit { | |
BYTES, | |
KILOBYTES, | |
MEGABYTES | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment