Skip to content

Commit

Permalink
Merge 7d944ad into ea7ce72
Browse files Browse the repository at this point in the history
  • Loading branch information
tstavinoha committed Jun 17, 2021
2 parents ea7ce72 + 7d944ad commit b1353ae
Show file tree
Hide file tree
Showing 29 changed files with 550 additions and 109 deletions.
1 change: 1 addition & 0 deletions .github/workflows/maven.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ jobs:
build:

runs-on: ubuntu-latest
concurrency: sequential

steps:
- uses: actions/checkout@v2
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
### 3.3.0
General:
* Added support for starting containers on a predefined port

### 3.2.0

#### ClickHouse:
Expand Down
16 changes: 12 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ For changes check the [changelog](CHANGELOG.md).
<a name="Usage"></a>
## Usage

<a name="General"></a>
### General

This library tries to reuse existing Spring Boot configuration classes and enhance their behaviour by performing some extra steps around them.
Generally, in cases where port placeholders are used (`<port>`), the library will make sure that the appropriate Docker container is started on
a randomly selected open port and that the selected value will be used by the configuration in the runtime.
You can use a concrete value instead of the placeholder - in that case the library will attempt to start the container on the specified port.

<a name="MSSQL"></a>
### MSSQL

Expand Down Expand Up @@ -86,7 +94,7 @@ spring:
Logical database is automatically created.
Container IP address is resolved based on running host, meaning on local machine `<host>` will resolve to `localhost`
while inside Docker placeholder will resolve to `containerIp`.
Docker container is mapped on random port so `<port>` placeholder is used and will be automatically substituted.
When `<port>` placeholder is used, container will be mapped on random port and automatically substituted.

<a name="MSSQLLocalDevelopment"></a>
#### Local Development:
Expand Down Expand Up @@ -169,7 +177,7 @@ spring:
Logical database is automatically created.
Container IP address is resolved based on running host, meaning on local machine `<host>` will resolve to `localhost`
while inside Docker placeholder will resolve to `containerIp`.
Docker container is mapped on random port so `<port>` placeholder is used and will be automatically substituted.
When `<port>` placeholder is used, container will be mapped on random port and automatically substituted.

<a name="PostgreSQLLocalDevelopment"></a>
#### Local Development:
Expand Down Expand Up @@ -242,7 +250,7 @@ spring:

Container IP address is resolved based on running host, meaning on local machine `<host>` will resolve to `localhost`
while inside Docker placeholder will resolve to `containerIp`.
Docker container is mapped on random port so `<port>` placeholder is used and will be automatically substituted.
When `<port>` placeholder is used, container will be mapped on random port and automatically substituted.

<a name="RedisDevelopment"></a>
#### Local Development:
Expand Down Expand Up @@ -323,7 +331,7 @@ spring:
Logical database is automatically created.
Container IP address is resolved based on running host, meaning on local machine `<host>` will resolve to `localhost`
while inside Docker placeholder will resolve to `containerIp`.
Docker container is mapped on random port so `<port>` placeholder is used and will be automatically substituted.
When `<port>` placeholder is used, container will be mapped on random port and automatically substituted.

<a name="KafkaLocalDevelopment"></a>
#### Local Development:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,20 @@ public class ClickhouseContainerInitializer extends InitializerBase<ClickhouseCo
public void initialize(ConfigurableApplicationContext applicationContext) {
Environment environment = applicationContext.getEnvironment();
Optional<String> customPropertyPath = Optional.ofNullable(environment.getProperty("testcontainers.clickhouse.custom-path"));
String jdbcUrlPropertyPath = customPropertyPath.orElse( "spring.datasource") + ".jdbc-url";
String jdbcUrlPropertyPath = customPropertyPath.orElse("spring.datasource") + ".jdbc-url";
String jdbcUrlValue = Objects.requireNonNull(environment.getProperty(jdbcUrlPropertyPath));
ClickhouseContainerWrapper container = Optional.ofNullable(
environment.getProperty("testcontainers.clickhouse.docker.image.version"))
.map(ClickhouseContainerWrapper::new)
.orElseGet(ClickhouseContainerWrapper::new);

resolveStaticPort(jdbcUrlValue, GENERIC_URL_WITH_PORT_GROUP_PATTERN)
.ifPresent(staticPort -> bindPort(container, staticPort, ClickhouseContainerWrapper.HTTP_PORT));

start(container);
String url = jdbcUrlValue.replace("<host>", container.getContainerIpAddress())
.replace("<port>", container.getMappedPort(ClickhouseContainerWrapper.HTTP_PORT)
.toString());

String url = replaceHostAndPortPlaceholders(jdbcUrlValue, container, ClickhouseContainerWrapper.HTTP_PORT);

TestPropertyValues values = TestPropertyValues.of(
String.format("%s=%s", jdbcUrlPropertyPath, url));
values.applyTo(applicationContext);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.infobip.testcontainers.spring.clickhouse;

import static com.infobip.testcontainers.spring.clickhouse.DataSourceConfig.CLICKHOUSE_URL_PROPERTY_NAME;
import static org.assertj.core.api.BDDAssertions.then;

import javax.sql.DataSource;

import lombok.AllArgsConstructor;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.TestConstructor;

@AllArgsConstructor
@ActiveProfiles("static-port")
@TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
@SpringBootTest(classes = Main.class)
class ClickhouseContainerInitializerWithStaticPortTest {

Environment environment;
DataSource dataSource;

@Test
void shouldCreateContainerWithStaticPort() {
//given
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);

//when
String result = jdbcTemplate.queryForObject("SELECT 1", String.class);

//then
then(result).isEqualTo("1");
}

@Test
void shouldResolveHostInUrl() {
// then
then(environment.getProperty(CLICKHOUSE_URL_PROPERTY_NAME)).isEqualTo("jdbc:clickhouse://localhost:5001");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,23 @@

import javax.sql.DataSource;

import lombok.AllArgsConstructor;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import ru.yandex.clickhouse.ClickHouseDriver;

@Configuration
@AllArgsConstructor
public class DataSourceConfig {

private final Environment environment;
static final String CLICKHOUSE_URL_PROPERTY_NAME = "spring.datasource.clickhouse.jdbc-url";

@Bean
public DataSource getDataSource() {
String url = environment.getProperty("spring.datasource.clickhouse.jdbc-url");
DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
dataSourceBuilder.driverClassName("ru.yandex.clickhouse.ClickHouseDriver");
dataSourceBuilder.url(url);
return dataSourceBuilder.build();
public DataSource getDataSource(Environment environment) {
return DataSourceBuilder.create()
.driverClassName(ClickHouseDriver.class.getName())
.url(environment.getProperty(CLICKHOUSE_URL_PROPERTY_NAME))
.build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
spring:
datasource:
clickhouse:
jdbc-url: jdbc:clickhouse://<host>:5001

testcontainers:
clickhouse:
custom-path: "spring.datasource.clickhouse"
Original file line number Diff line number Diff line change
@@ -1,39 +1,47 @@
package com.infobip.testcontainers.spring.kafka;

import static java.lang.Integer.parseInt;
import static java.lang.Short.parseShort;

import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.infobip.testcontainers.InitializerBase;
import org.apache.kafka.clients.admin.AdminClient;
import org.apache.kafka.clients.admin.NewTopic;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.Environment;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.lang.Integer.parseInt;
import static java.lang.Short.parseShort;

public class KafkaContainerInitializer extends InitializerBase<KafkaContainerWrapper> {

private static final Pattern KAFKA_SERVER_PATTERN = Pattern.compile("^.*:(\\d+).*");

@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
Environment environment = applicationContext.getEnvironment();
String bootstrapServers = Objects.requireNonNull(environment.getProperty("spring.kafka.bootstrap-servers"));
KafkaContainerWrapper container = Optional.ofNullable(
environment.getProperty("testcontainers.kafka.docker.image.version"))
environment.getProperty("testcontainers.kafka.docker.image.version"))
.map(KafkaContainerWrapper::new)
.orElseGet(KafkaContainerWrapper::new);

resolveStaticPort(bootstrapServers, KAFKA_SERVER_PATTERN)
.ifPresent(staticPort -> bindPort(container, staticPort, KafkaContainerWrapper.KAFKA_PORT));

start(container);
String url = bootstrapServers.replace("<host>", container.getContainerIpAddress())
.replace("<port>", container.getMappedPort(KafkaContainerWrapper.KAFKA_PORT)
.toString());
String url = replaceHostAndPortPlaceholders(bootstrapServers, container, KafkaContainerWrapper.KAFKA_PORT);

Optional.ofNullable(environment.getProperty("testcontainers.kafka.topics", String[].class))
.ifPresent(topics -> createTestKafkaTopics(url, topics));
TestPropertyValues values = TestPropertyValues.of(
"spring.kafka.bootstrap-servers=" + url);
"spring.kafka.bootstrap-servers=" + url);
values.applyTo(applicationContext);
}

Expand All @@ -54,4 +62,5 @@ private static void createTestKafkaTopics(String bootstrapServers, String[] topi
throw new RuntimeException(e);
}
}

}
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
package com.infobip.testcontainers.spring.kafka;

import static org.assertj.core.api.BDDAssertions.then;
import static org.awaitility.Awaitility.await;

import java.time.Duration;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import lombok.AllArgsConstructor;
import org.awaitility.Awaitility;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.support.SendResult;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.TestConstructor;

import java.time.Duration;
import java.util.Objects;
import java.util.concurrent.*;

import static org.assertj.core.api.BDDAssertions.then;

@AllArgsConstructor
@ActiveProfiles("test")
@TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
Expand All @@ -28,26 +29,18 @@ class KafkaContainerInitializerTest {

@Test
void shouldCreateContainer() throws InterruptedException, ExecutionException, TimeoutException {

// given
String givenData = "givenData";
String givenValue = this.getClass().getName();

// when
SendResult<?, ?> actual = kafkaTemplate.send(TOPIC_NAME, "key", givenData)
SendResult<?, ?> actual = kafkaTemplate.send(TOPIC_NAME, "key", givenValue)
.completable()
.get(10, TimeUnit.SECONDS);

// then
then(actual).isNotNull();
Awaitility.await().atMost(Duration.ofSeconds(10)).until(() -> {
String value = listener.getValue();

if(Objects.isNull(value)) {
return false;
}

then(value).isEqualTo(givenData);
return true;
});
await().atMost(Duration.ofSeconds(10))
.untilAsserted(() -> then(listener.getValue()).isEqualTo(givenValue));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.infobip.testcontainers.spring.kafka;

import static org.assertj.core.api.BDDAssertions.then;
import static org.awaitility.Awaitility.await;

import java.time.Duration;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import lombok.AllArgsConstructor;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.kafka.KafkaProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.support.SendResult;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.TestConstructor;

@AllArgsConstructor
@ActiveProfiles("static-port")
@TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
@SpringBootTest(classes = Main.class)
class KafkaContainerInitializerWithStaticPortTest {

final static String TOPIC_NAME = "test-topic";

private final KafkaTemplate<String, String> kafkaTemplate;
private final Listener listener;
private final KafkaProperties kafkaProperties;

@Test
void shouldCreateContainer() throws InterruptedException, ExecutionException, TimeoutException {
// given
String givenValue = this.getClass().getName();

// when
SendResult<?, ?> actual = kafkaTemplate.send(TOPIC_NAME, "key", givenValue)
.completable()
.get(10, TimeUnit.SECONDS);

// then
then(actual).isNotNull();
await().atMost(Duration.ofSeconds(10))
.untilAsserted(() -> then(listener.getValue()).isEqualTo(givenValue));
}

@Test
void shouldResolveHostInUrl() {
// then
then(kafkaProperties.getBootstrapServers()).containsExactly("localhost:5002");
}

}
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
package com.infobip.testcontainers.spring.kafka;

import java.util.concurrent.atomic.AtomicReference;

import lombok.AllArgsConstructor;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.atomic.AtomicReference;

@AllArgsConstructor
@Component
class Listener {
Expand All @@ -23,4 +21,5 @@ public void handle(@Payload String value) {
String getValue() {
return value.get();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
spring:
kafka:
consumer:
groupId: static
autoOffsetReset: earliest
bootstrap-servers: <host>:5002

testcontainers.kafka.topics: test-topic:1:1
Loading

0 comments on commit b1353ae

Please sign in to comment.