Skip to content

Instantly share code, notes, and snippets.

@dehidehidehi
Created January 9, 2024 17:21
Show Gist options
  • Save dehidehidehi/bdd2b35c2124991df8becb16245eb038 to your computer and use it in GitHub Desktop.
Save dehidehidehi/bdd2b35c2124991df8becb16245eb038 to your computer and use it in GitHub Desktop.
TestContainers class setup
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 {
}
}
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