diff --git a/servicebus/spring-cloud-azure-stream-binder-servicebus/servicebus-queue-binder/pom.xml b/servicebus/spring-cloud-azure-stream-binder-servicebus/servicebus-queue-binder/pom.xml
index b5fb365be..82adf58f8 100644
--- a/servicebus/spring-cloud-azure-stream-binder-servicebus/servicebus-queue-binder/pom.xml
+++ b/servicebus/spring-cloud-azure-stream-binder-servicebus/servicebus-queue-binder/pom.xml
@@ -21,7 +21,7 @@
17
17
- 7.0.0
+ 7.1.0-beta.1
@@ -60,6 +60,46 @@
org.hibernate.validator
hibernate-validator
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ com.azure.spring
+ spring-cloud-azure-autoconfigure
+ test
+
+
+ com.azure.spring
+ spring-cloud-azure-docker-compose
+ test
+
+
+ org.springframework.boot
+ spring-boot-testcontainers
+ test
+
+
+ org.testcontainers
+ testcontainers-azure
+ test
+
+
+ org.testcontainers
+ testcontainers-junit-jupiter
+ test
+
+
+ com.azure.spring
+ spring-cloud-azure-testcontainers
+ test
+
+
+ com.microsoft.sqlserver
+ mssql-jdbc
+ test
+
diff --git a/servicebus/spring-cloud-azure-stream-binder-servicebus/servicebus-queue-binder/src/test/java/com/azure/spring/sample/servicebus/queue/binder/ServiceBusDockerComposeTest.java b/servicebus/spring-cloud-azure-stream-binder-servicebus/servicebus-queue-binder/src/test/java/com/azure/spring/sample/servicebus/queue/binder/ServiceBusDockerComposeTest.java
new file mode 100644
index 000000000..66e9c6771
--- /dev/null
+++ b/servicebus/spring-cloud-azure-stream-binder-servicebus/servicebus-queue-binder/src/test/java/com/azure/spring/sample/servicebus/queue/binder/ServiceBusDockerComposeTest.java
@@ -0,0 +1,94 @@
+package com.azure.spring.sample.servicebus.queue.binder;
+
+import com.azure.spring.cloud.autoconfigure.implementation.context.AzureGlobalPropertiesAutoConfiguration;
+import com.azure.spring.cloud.autoconfigure.implementation.servicebus.AzureServiceBusAutoConfiguration;
+import com.azure.spring.cloud.autoconfigure.implementation.servicebus.AzureServiceBusMessagingAutoConfiguration;
+import com.azure.spring.messaging.checkpoint.Checkpointer;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.support.MessageBuilder;
+
+import java.time.Duration;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+import static com.azure.spring.messaging.AzureHeaders.CHECKPOINTER;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Awaitility.waitAtMost;
+
+@SpringBootTest(properties = {
+ "spring.docker.compose.skip.in-tests=false",
+ "spring.docker.compose.file=classpath:servicebus-compose.yaml",
+ "spring.docker.compose.stop.command=down",
+ "spring.cloud.function.definition=consume;supply",
+ "spring.cloud.stream.bindings.consume-in-0.destination=queue.1",
+ "spring.cloud.stream.bindings.supply-out-0.destination=queue.1",
+ "spring.cloud.stream.servicebus.bindings.consume-in-0.consumer.auto-complete=false",
+ "spring.cloud.stream.servicebus.bindings.supply-out-0.producer.entity-type=queue",
+ "spring.cloud.stream.poller.fixed-delay=1000",
+ "spring.cloud.stream.poller.initial-delay=0"
+})
+class ServiceBusDockerComposeTest {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(ServiceBusDockerComposeTest.class);
+ private static final Set RECEIVED_MESSAGES = ConcurrentHashMap.newKeySet();
+ private static final AtomicInteger messageSequence = new AtomicInteger(0);
+
+ @Test
+ void supplierAndConsumerShouldWorkThroughServiceBusQueue() {
+ waitAtMost(Duration.ofSeconds(60))
+ .pollDelay(Duration.ofSeconds(2))
+ .untilAsserted(() -> {
+ assertThat(RECEIVED_MESSAGES).isNotEmpty();
+ LOGGER.info("✓ Test passed - Consumer received {} message(s)", RECEIVED_MESSAGES.size());
+ });
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ @EnableAutoConfiguration
+ @ImportAutoConfiguration(classes = {
+ AzureGlobalPropertiesAutoConfiguration.class,
+ AzureServiceBusAutoConfiguration.class,
+ AzureServiceBusMessagingAutoConfiguration.class})
+ static class Config {
+
+ @Bean
+ public Supplier> supply() {
+ return () -> {
+ int sequence = messageSequence.getAndIncrement();
+ String payload = "Hello world, " + sequence;
+ LOGGER.info("[Supplier] Invoked - message sequence: {}", sequence);
+ return MessageBuilder.withPayload(payload).build();
+ };
+ }
+
+ @Bean
+ public Consumer> consume() {
+ return message -> {
+ String payload = message.getPayload();
+ RECEIVED_MESSAGES.add(payload);
+ LOGGER.info("[Consumer] Received message: {}", payload);
+
+ Checkpointer checkpointer = (Checkpointer) message.getHeaders().get(CHECKPOINTER);
+ if (checkpointer != null) {
+ checkpointer.success()
+ .doOnSuccess(s -> LOGGER.info("[Consumer] Message checkpointed"))
+ .doOnError(e -> LOGGER.error("[Consumer] Checkpoint failed", e))
+ .block();
+ }
+ };
+ }
+ }
+}
+
+
diff --git a/servicebus/spring-cloud-azure-stream-binder-servicebus/servicebus-queue-binder/src/test/java/com/azure/spring/sample/servicebus/queue/binder/ServiceBusTestContainerTest.java b/servicebus/spring-cloud-azure-stream-binder-servicebus/servicebus-queue-binder/src/test/java/com/azure/spring/sample/servicebus/queue/binder/ServiceBusTestContainerTest.java
new file mode 100644
index 000000000..47c489d99
--- /dev/null
+++ b/servicebus/spring-cloud-azure-stream-binder-servicebus/servicebus-queue-binder/src/test/java/com/azure/spring/sample/servicebus/queue/binder/ServiceBusTestContainerTest.java
@@ -0,0 +1,117 @@
+package com.azure.spring.sample.servicebus.queue.binder;
+
+import com.azure.spring.cloud.autoconfigure.implementation.context.AzureGlobalPropertiesAutoConfiguration;
+import com.azure.spring.cloud.autoconfigure.implementation.servicebus.AzureServiceBusAutoConfiguration;
+import com.azure.spring.cloud.autoconfigure.implementation.servicebus.AzureServiceBusMessagingAutoConfiguration;
+import com.azure.spring.messaging.checkpoint.Checkpointer;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
+import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.support.MessageBuilder;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
+import org.testcontainers.azure.ServiceBusEmulatorContainer;
+import org.testcontainers.containers.MSSQLServerContainer;
+import org.testcontainers.containers.Network;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+import org.testcontainers.utility.MountableFile;
+
+import java.time.Duration;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+import static com.azure.spring.messaging.AzureHeaders.CHECKPOINTER;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Awaitility.waitAtMost;
+
+@SpringJUnitConfig
+@TestPropertySource(properties = {
+ "spring.cloud.function.definition=consume;supply",
+ "spring.cloud.stream.bindings.consume-in-0.destination=queue.1",
+ "spring.cloud.stream.bindings.supply-out-0.destination=queue.1",
+ "spring.cloud.stream.servicebus.bindings.consume-in-0.consumer.auto-complete=false",
+ "spring.cloud.stream.servicebus.bindings.supply-out-0.producer.entity-type=queue",
+ "spring.cloud.stream.poller.fixed-delay=1000",
+ "spring.cloud.stream.poller.initial-delay=0"})
+@Testcontainers
+class ServiceBusTestContainerTest {
+
+ private static final Network NETWORK = Network.newNetwork();
+
+ private static final MSSQLServerContainer> SQLSERVER = new MSSQLServerContainer<>(
+ "mcr.microsoft.com/mssql/server:2022-CU14-ubuntu-22.04")
+ .acceptLicense()
+ .withNetwork(NETWORK)
+ .withNetworkAliases("sqlserver");
+
+ @Container
+ @ServiceConnection
+ private static final ServiceBusEmulatorContainer SERVICE_BUS = new ServiceBusEmulatorContainer(
+ "mcr.microsoft.com/azure-messaging/servicebus-emulator:latest")
+ .acceptLicense()
+ .withCopyFileToContainer(MountableFile.forClasspathResource("Config.json"),
+ "/ServiceBus_Emulator/ConfigFiles/Config.json")
+ .withNetwork(NETWORK)
+ .withMsSqlServerContainer(SQLSERVER);
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(ServiceBusDockerComposeTest.class);
+ private static final Set RECEIVED_MESSAGES = ConcurrentHashMap.newKeySet();
+ private static final AtomicInteger messageSequence = new AtomicInteger(0);
+
+ @Test
+ void supplierAndConsumerShouldWorkThroughServiceBusQueue() {
+ waitAtMost(Duration.ofSeconds(60))
+ .pollDelay(Duration.ofSeconds(2))
+ .untilAsserted(() -> {
+ assertThat(RECEIVED_MESSAGES).isNotEmpty();
+ LOGGER.info("✓ Test passed - Consumer received {} message(s)", RECEIVED_MESSAGES.size());
+ });
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ @EnableAutoConfiguration
+ @ImportAutoConfiguration(classes = {
+ AzureGlobalPropertiesAutoConfiguration.class,
+ AzureServiceBusAutoConfiguration.class,
+ AzureServiceBusMessagingAutoConfiguration.class})
+ static class Config {
+
+ @Bean
+ public Supplier> supply() {
+ return () -> {
+ int sequence = messageSequence.getAndIncrement();
+ String payload = "Hello world, " + sequence;
+ LOGGER.info("[Supplier] Invoked - message sequence: {}", sequence);
+ return MessageBuilder.withPayload(payload).build();
+ };
+ }
+
+ @Bean
+ public Consumer> consume() {
+ return message -> {
+ String payload = message.getPayload();
+ RECEIVED_MESSAGES.add(payload);
+ LOGGER.info("[Consumer] Received message: {}", payload);
+
+ Checkpointer checkpointer = (Checkpointer) message.getHeaders().get(CHECKPOINTER);
+ if (checkpointer != null) {
+ checkpointer.success()
+ .doOnSuccess(s -> LOGGER.info("[Consumer] Message checkpointed"))
+ .doOnError(e -> LOGGER.error("[Consumer] Checkpoint failed", e))
+ .block();
+ }
+ };
+ }
+ }
+}
+
diff --git a/servicebus/spring-cloud-azure-stream-binder-servicebus/servicebus-queue-binder/src/test/resources/Config.json b/servicebus/spring-cloud-azure-stream-binder-servicebus/servicebus-queue-binder/src/test/resources/Config.json
new file mode 100644
index 000000000..45c368034
--- /dev/null
+++ b/servicebus/spring-cloud-azure-stream-binder-servicebus/servicebus-queue-binder/src/test/resources/Config.json
@@ -0,0 +1,108 @@
+{
+ "UserConfig": {
+ "Namespaces": [
+ {
+ "Name": "sbemulatorns",
+ "Queues": [
+ {
+ "Name": "queue.1",
+ "Properties": {
+ "DeadLetteringOnMessageExpiration": false,
+ "DefaultMessageTimeToLive": "PT1H",
+ "DuplicateDetectionHistoryTimeWindow": "PT20S",
+ "ForwardDeadLetteredMessagesTo": "",
+ "ForwardTo": "",
+ "LockDuration": "PT1M",
+ "MaxDeliveryCount": 10,
+ "RequiresDuplicateDetection": false,
+ "RequiresSession": false
+ }
+ }
+ ],
+
+ "Topics": [
+ {
+ "Name": "topic.1",
+ "Properties": {
+ "DefaultMessageTimeToLive": "PT1H",
+ "DuplicateDetectionHistoryTimeWindow": "PT20S",
+ "RequiresDuplicateDetection": false
+ },
+ "Subscriptions": [
+ {
+ "Name": "subscription.1",
+ "Properties": {
+ "DeadLetteringOnMessageExpiration": false,
+ "DefaultMessageTimeToLive": "PT1H",
+ "LockDuration": "PT1M",
+ "MaxDeliveryCount": 10,
+ "ForwardDeadLetteredMessagesTo": "",
+ "ForwardTo": "",
+ "RequiresSession": false
+ },
+ "Rules": [
+ {
+ "Name": "app-prop-filter-1",
+ "Properties": {
+ "FilterType": "Correlation",
+ "CorrelationFilter": {
+ "ContentType": "application/text",
+ "CorrelationId": "id1",
+ "Label": "subject1",
+ "MessageId": "msgid1",
+ "ReplyTo": "someQueue",
+ "ReplyToSessionId": "sessionId",
+ "SessionId": "session1",
+ "To": "xyz"
+ }
+ }
+ }
+ ]
+ },
+ {
+ "Name": "subscription.2",
+ "Properties": {
+ "DeadLetteringOnMessageExpiration": false,
+ "DefaultMessageTimeToLive": "PT1H",
+ "LockDuration": "PT1M",
+ "MaxDeliveryCount": 10,
+ "ForwardDeadLetteredMessagesTo": "",
+ "ForwardTo": "",
+ "RequiresSession": false
+ },
+ "Rules": [
+ {
+ "Name": "user-prop-filter-1",
+ "Properties": {
+ "FilterType": "Correlation",
+ "CorrelationFilter": {
+ "Properties": {
+ "prop3": "value3"
+ }
+ }
+ }
+ }
+ ]
+ },
+ {
+ "Name": "subscription.3",
+ "Properties": {
+ "DeadLetteringOnMessageExpiration": false,
+ "DefaultMessageTimeToLive": "PT1H",
+ "LockDuration": "PT1M",
+ "MaxDeliveryCount": 10,
+ "ForwardDeadLetteredMessagesTo": "",
+ "ForwardTo": "",
+ "RequiresSession": false
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "Logging": {
+ "Type": "File"
+ }
+ }
+}
\ No newline at end of file
diff --git a/servicebus/spring-cloud-azure-stream-binder-servicebus/servicebus-queue-binder/src/test/resources/application.yaml b/servicebus/spring-cloud-azure-stream-binder-servicebus/servicebus-queue-binder/src/test/resources/application.yaml
new file mode 100644
index 000000000..2ab1a8377
--- /dev/null
+++ b/servicebus/spring-cloud-azure-stream-binder-servicebus/servicebus-queue-binder/src/test/resources/application.yaml
@@ -0,0 +1,24 @@
+spring:
+ cloud:
+ azure:
+ servicebus:
+ namespace:
+ function:
+ definition: consume;supply
+ stream:
+ bindings:
+ consume-in-0:
+ destination:
+ supply-out-0:
+ destination:
+ servicebus:
+ bindings:
+ consume-in-0:
+ consumer:
+ auto-complete: false
+ supply-out-0:
+ producer:
+ entity-type: queue
+ poller:
+ fixed-delay: 1000
+ initial-delay: 0
diff --git a/servicebus/spring-cloud-azure-stream-binder-servicebus/servicebus-queue-binder/src/test/resources/servicebus-compose.yaml b/servicebus/spring-cloud-azure-stream-binder-servicebus/servicebus-queue-binder/src/test/resources/servicebus-compose.yaml
new file mode 100644
index 000000000..bda36d124
--- /dev/null
+++ b/servicebus/spring-cloud-azure-stream-binder-servicebus/servicebus-queue-binder/src/test/resources/servicebus-compose.yaml
@@ -0,0 +1,30 @@
+services:
+ servicebus:
+ image: mcr.microsoft.com/azure-messaging/servicebus-emulator:latest
+ pull_policy: always
+ volumes:
+ - "./Config.json:/ServiceBus_Emulator/ConfigFiles/Config.json"
+ ports:
+ - "5672"
+ environment:
+ SQL_SERVER: sqledge
+ MSSQL_SA_PASSWORD: A_Str0ng_Required_Password
+ ACCEPT_EULA: Y
+ depends_on:
+ - sqledge
+ networks:
+ sb-emulator:
+ aliases:
+ - "sb-emulator"
+ sqledge:
+ image: "mcr.microsoft.com/azure-sql-edge:latest"
+ networks:
+ sb-emulator:
+ aliases:
+ - "sqledge"
+ environment:
+ ACCEPT_EULA: Y
+ MSSQL_SA_PASSWORD: A_Str0ng_Required_Password
+
+networks:
+ sb-emulator:
\ No newline at end of file
diff --git a/servicebus/spring-messaging-azure-servicebus/servicebus-spring-messaging/pom.xml b/servicebus/spring-messaging-azure-servicebus/servicebus-spring-messaging/pom.xml
index e4195999b..f6072430a 100644
--- a/servicebus/spring-messaging-azure-servicebus/servicebus-spring-messaging/pom.xml
+++ b/servicebus/spring-messaging-azure-servicebus/servicebus-spring-messaging/pom.xml
@@ -22,7 +22,7 @@
17
17
- 7.0.0
+ 7.1.0-beta.1
@@ -46,6 +46,43 @@
com.azure.spring
spring-cloud-azure-starter
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.springframework.boot
+ spring-boot-testcontainers
+ test
+
+
+ org.testcontainers
+ azure
+ 1.21.3
+ test
+
+
+ org.testcontainers
+ junit-jupiter
+ 1.21.3
+ test
+
+
+ com.azure.spring
+ spring-cloud-azure-docker-compose
+ test
+
+
+ com.azure.spring
+ spring-cloud-azure-testcontainers
+ test
+
+
+ com.microsoft.sqlserver
+ mssql-jdbc
+ test
+
diff --git a/servicebus/spring-messaging-azure-servicebus/servicebus-spring-messaging/src/test/java/com/azure/spring/sample/servicebus/messaging/ServiceBusDockerComposeTest.java b/servicebus/spring-messaging-azure-servicebus/servicebus-spring-messaging/src/test/java/com/azure/spring/sample/servicebus/messaging/ServiceBusDockerComposeTest.java
new file mode 100644
index 000000000..d274ff9fe
--- /dev/null
+++ b/servicebus/spring-messaging-azure-servicebus/servicebus-spring-messaging/src/test/java/com/azure/spring/sample/servicebus/messaging/ServiceBusDockerComposeTest.java
@@ -0,0 +1,109 @@
+package com.azure.spring.sample.servicebus.messaging;
+
+
+import com.azure.messaging.servicebus.ServiceBusMessage;
+import com.azure.messaging.servicebus.ServiceBusSenderClient;
+import com.azure.spring.cloud.autoconfigure.implementation.context.AzureGlobalPropertiesAutoConfiguration;
+import com.azure.spring.cloud.autoconfigure.implementation.servicebus.AzureServiceBusAutoConfiguration;
+import com.azure.spring.cloud.autoconfigure.implementation.servicebus.AzureServiceBusMessagingAutoConfiguration;
+import com.azure.spring.cloud.autoconfigure.implementation.servicebus.properties.AzureServiceBusConnectionDetails;
+import com.azure.spring.cloud.service.servicebus.consumer.ServiceBusErrorHandler;
+import com.azure.spring.cloud.service.servicebus.consumer.ServiceBusRecordMessageListener;
+import com.azure.spring.messaging.servicebus.core.ServiceBusTemplate;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.messaging.support.MessageBuilder;
+
+import java.time.Duration;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Awaitility.waitAtMost;
+
+@SpringBootTest(properties = {
+ "spring.docker.compose.skip.in-tests=false",
+ "spring.docker.compose.file=classpath:servicebus-compose.yaml",
+ "spring.docker.compose.stop.command=down",
+ "spring.docker.compose.readiness.timeout=PT5M",
+ "spring.cloud.azure.servicebus.namespace=sbemulatorns",
+ "spring.cloud.azure.servicebus.entity-name=queue.1",
+ "spring.cloud.azure.servicebus.entity-type=queue",
+ "spring.cloud.azure.servicebus.producer.entity-name=queue.1",
+ "spring.cloud.azure.servicebus.producer.entity-type=queue",
+ "spring.cloud.azure.servicebus.processor.entity-name=queue.1",
+ "spring.cloud.azure.servicebus.processor.entity-type=queue"
+})
+class ServiceBusDockerComposeTest {
+
+ @Autowired
+ private AzureServiceBusConnectionDetails connectionDetails;
+
+ @Autowired
+ private ServiceBusSenderClient senderClient;
+
+ @Autowired
+ private ServiceBusTemplate serviceBusTemplate;
+
+ @Test
+ void connectionDetailsShouldBeProvidedByFactory() {
+ assertThat(connectionDetails).isNotNull();
+ assertThat(connectionDetails.getConnectionString())
+ .isNotBlank()
+ .startsWith("Endpoint=sb://");
+ }
+
+ @Test
+ void senderClientCanSendMessage() {
+ // Wait for Service Bus emulator to be fully ready and queue entity to be available
+ // The emulator depends on SQL Edge and needs time to initialize the messaging entities
+ waitAtMost(Duration.ofSeconds(120)).pollInterval(Duration.ofSeconds(2)).untilAsserted(() -> {
+ this.senderClient.sendMessage(new ServiceBusMessage("Hello World!"));
+ });
+
+ waitAtMost(Duration.ofSeconds(30)).pollDelay(Duration.ofSeconds(5)).untilAsserted(() -> {
+ assertThat(Config.MESSAGES).contains("Hello World!");
+ });
+ }
+
+ @Test
+ void serviceBusTemplateCanSendMessage() {
+ // Wait for Service Bus emulator to be fully ready and queue entity to be available
+ // The emulator depends on SQL Edge and needs time to initialize the messaging entities
+ waitAtMost(Duration.ofSeconds(120)).pollInterval(Duration.ofSeconds(2)).untilAsserted(() -> {
+ this.serviceBusTemplate.sendAsync("queue.1", MessageBuilder.withPayload("Hello from ServiceBusTemplate!").build()).block();
+ });
+
+ waitAtMost(Duration.ofSeconds(30)).pollDelay(Duration.ofSeconds(5)).untilAsserted(() -> {
+ assertThat(Config.MESSAGES).contains("Hello from ServiceBusTemplate!");
+ });
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ @ImportAutoConfiguration(classes = {
+ AzureGlobalPropertiesAutoConfiguration.class,
+ AzureServiceBusAutoConfiguration.class,
+ AzureServiceBusMessagingAutoConfiguration.class})
+ static class Config {
+
+ private static final Set MESSAGES = ConcurrentHashMap.newKeySet();
+
+ @Bean
+ ServiceBusRecordMessageListener processMessage() {
+ return context -> {
+ MESSAGES.add(context.getMessage().getBody().toString());
+ };
+ }
+
+ @Bean
+ ServiceBusErrorHandler errorHandler() {
+ // No-op error handler for tests: acknowledge errors without affecting test execution.
+ return (context) -> {
+ };
+ }
+ }
+}
diff --git a/servicebus/spring-messaging-azure-servicebus/servicebus-spring-messaging/src/test/java/com/azure/spring/sample/servicebus/messaging/ServiceBusTestContainerTest.java b/servicebus/spring-messaging-azure-servicebus/servicebus-spring-messaging/src/test/java/com/azure/spring/sample/servicebus/messaging/ServiceBusTestContainerTest.java
new file mode 100644
index 000000000..65c8ea871
--- /dev/null
+++ b/servicebus/spring-messaging-azure-servicebus/servicebus-spring-messaging/src/test/java/com/azure/spring/sample/servicebus/messaging/ServiceBusTestContainerTest.java
@@ -0,0 +1,125 @@
+package com.azure.spring.sample.servicebus.messaging;
+
+import com.azure.messaging.servicebus.ServiceBusMessage;
+import com.azure.messaging.servicebus.ServiceBusSenderClient;
+import com.azure.spring.cloud.autoconfigure.implementation.context.AzureGlobalPropertiesAutoConfiguration;
+import com.azure.spring.cloud.autoconfigure.implementation.servicebus.AzureServiceBusAutoConfiguration;
+import com.azure.spring.cloud.autoconfigure.implementation.servicebus.AzureServiceBusMessagingAutoConfiguration;
+import com.azure.spring.cloud.autoconfigure.implementation.servicebus.properties.AzureServiceBusConnectionDetails;
+import com.azure.spring.cloud.service.servicebus.consumer.ServiceBusErrorHandler;
+import com.azure.spring.cloud.service.servicebus.consumer.ServiceBusRecordMessageListener;
+import com.azure.spring.messaging.servicebus.core.ServiceBusTemplate;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
+import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.messaging.support.MessageBuilder;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
+import org.testcontainers.azure.ServiceBusEmulatorContainer;
+import org.testcontainers.containers.MSSQLServerContainer;
+import org.testcontainers.containers.Network;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+import org.testcontainers.utility.MountableFile;
+
+import java.time.Duration;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Awaitility.waitAtMost;
+
+@SpringJUnitConfig
+@TestPropertySource(properties = { "spring.cloud.azure.servicebus.entity-name=queue.1",
+ "spring.cloud.azure.servicebus.entity-type=queue" })
+@Testcontainers
+class ServiceBusTestContainerTest {
+
+ private static final Network NETWORK = Network.newNetwork();
+
+ private static final MSSQLServerContainer> SQLSERVER = new MSSQLServerContainer<>(
+ "mcr.microsoft.com/mssql/server:2022-CU14-ubuntu-22.04")
+ .acceptLicense()
+ .withNetwork(NETWORK)
+ .withNetworkAliases("sqlserver");
+
+ @Container
+ @ServiceConnection
+ private static final ServiceBusEmulatorContainer SERVICE_BUS = new ServiceBusEmulatorContainer(
+ "mcr.microsoft.com/azure-messaging/servicebus-emulator:latest")
+ .acceptLicense()
+ .withCopyFileToContainer(MountableFile.forClasspathResource("Config.json"),
+ "/ServiceBus_Emulator/ConfigFiles/Config.json")
+ .withNetwork(NETWORK)
+ .withMsSqlServerContainer(SQLSERVER);
+
+ @Autowired
+ private AzureServiceBusConnectionDetails connectionDetails;
+
+ @Autowired
+ private ServiceBusSenderClient senderClient;
+
+ @Autowired
+ private ServiceBusTemplate serviceBusTemplate;
+
+ @Test
+ void connectionDetailsShouldBeProvidedByFactory() {
+ assertThat(connectionDetails).isNotNull();
+ assertThat(connectionDetails.getConnectionString())
+ .isNotBlank()
+ .startsWith("Endpoint=sb://");
+ }
+
+ @Test
+ void senderClientCanSendMessage() {
+ // Wait for Service Bus emulator to be fully ready and queue entity to be available
+ waitAtMost(Duration.ofSeconds(120)).pollInterval(Duration.ofSeconds(2)).untilAsserted(() -> {
+ this.senderClient.sendMessage(new ServiceBusMessage("Hello World!"));
+ });
+
+ waitAtMost(Duration.ofSeconds(30)).untilAsserted(() -> {
+ assertThat(Config.MESSAGES).contains("Hello World!");
+ });
+ }
+
+ @Test
+ void serviceBusTemplateCanSendMessage() {
+ // Wait for Service Bus emulator to be fully ready and queue entity to be available
+ waitAtMost(Duration.ofSeconds(120)).pollInterval(Duration.ofSeconds(2)).untilAsserted(() -> {
+ this.serviceBusTemplate.sendAsync("queue.1", MessageBuilder.withPayload("Hello from ServiceBusTemplate!").build()).block();
+ });
+
+ waitAtMost(Duration.ofSeconds(30)).untilAsserted(() -> {
+ assertThat(Config.MESSAGES).contains("Hello from ServiceBusTemplate!");
+ });
+ }
+
+
+ @Configuration(proxyBeanMethods = false)
+ @ImportAutoConfiguration(classes = {AzureGlobalPropertiesAutoConfiguration.class,
+ AzureServiceBusAutoConfiguration.class,
+ AzureServiceBusMessagingAutoConfiguration.class})
+ static class Config {
+
+ private static final Set MESSAGES = ConcurrentHashMap.newKeySet();
+
+ @Bean
+ ServiceBusRecordMessageListener processMessage() {
+ return context -> {
+ MESSAGES.add(context.getMessage().getBody().toString());
+ };
+ }
+
+ @Bean
+ ServiceBusErrorHandler errorHandler() {
+ // No-op error handler for tests: acknowledge errors without affecting test execution.
+ return (context) -> {
+ };
+ }
+
+ }
+}
+
diff --git a/servicebus/spring-messaging-azure-servicebus/servicebus-spring-messaging/src/test/resources/Config.json b/servicebus/spring-messaging-azure-servicebus/servicebus-spring-messaging/src/test/resources/Config.json
new file mode 100644
index 000000000..256f4fbc4
--- /dev/null
+++ b/servicebus/spring-messaging-azure-servicebus/servicebus-spring-messaging/src/test/resources/Config.json
@@ -0,0 +1,108 @@
+{
+ "UserConfig": {
+ "Namespaces": [
+ {
+ "Name": "sbemulatorns",
+ "Queues": [
+ {
+ "Name": "queue.1",
+ "Properties": {
+ "DeadLetteringOnMessageExpiration": false,
+ "DefaultMessageTimeToLive": "PT1H",
+ "DuplicateDetectionHistoryTimeWindow": "PT20S",
+ "ForwardDeadLetteredMessagesTo": "",
+ "ForwardTo": "",
+ "LockDuration": "PT1M",
+ "MaxDeliveryCount": 10,
+ "RequiresDuplicateDetection": false,
+ "RequiresSession": false
+ }
+ }
+ ],
+
+ "Topics": [
+ {
+ "Name": "topic.1",
+ "Properties": {
+ "DefaultMessageTimeToLive": "PT1H",
+ "DuplicateDetectionHistoryTimeWindow": "PT20S",
+ "RequiresDuplicateDetection": false
+ },
+ "Subscriptions": [
+ {
+ "Name": "subscription.1",
+ "Properties": {
+ "DeadLetteringOnMessageExpiration": false,
+ "DefaultMessageTimeToLive": "PT1H",
+ "LockDuration": "PT1M",
+ "MaxDeliveryCount": 10,
+ "ForwardDeadLetteredMessagesTo": "",
+ "ForwardTo": "",
+ "RequiresSession": false
+ },
+ "Rules": [
+ {
+ "Name": "app-prop-filter-1",
+ "Properties": {
+ "FilterType": "Correlation",
+ "CorrelationFilter": {
+ "ContentType": "application/text",
+ "CorrelationId": "id1",
+ "Label": "subject1",
+ "MessageId": "msgid1",
+ "ReplyTo": "someQueue",
+ "ReplyToSessionId": "sessionId",
+ "SessionId": "session1",
+ "To": "xyz"
+ }
+ }
+ }
+ ]
+ },
+ {
+ "Name": "subscription.2",
+ "Properties": {
+ "DeadLetteringOnMessageExpiration": false,
+ "DefaultMessageTimeToLive": "PT1H",
+ "LockDuration": "PT1M",
+ "MaxDeliveryCount": 10,
+ "ForwardDeadLetteredMessagesTo": "",
+ "ForwardTo": "",
+ "RequiresSession": false
+ },
+ "Rules": [
+ {
+ "Name": "user-prop-filter-1",
+ "Properties": {
+ "FilterType": "Correlation",
+ "CorrelationFilter": {
+ "Properties": {
+ "prop3": "value3"
+ }
+ }
+ }
+ }
+ ]
+ },
+ {
+ "Name": "subscription.3",
+ "Properties": {
+ "DeadLetteringOnMessageExpiration": false,
+ "DefaultMessageTimeToLive": "PT1H",
+ "LockDuration": "PT1M",
+ "MaxDeliveryCount": 10,
+ "ForwardDeadLetteredMessagesTo": "",
+ "ForwardTo": "",
+ "RequiresSession": false
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "Logging": {
+ "Type": "File"
+ }
+ }
+}
diff --git a/servicebus/spring-messaging-azure-servicebus/servicebus-spring-messaging/src/test/resources/servicebus-compose.yaml b/servicebus/spring-messaging-azure-servicebus/servicebus-spring-messaging/src/test/resources/servicebus-compose.yaml
new file mode 100644
index 000000000..54c765b69
--- /dev/null
+++ b/servicebus/spring-messaging-azure-servicebus/servicebus-spring-messaging/src/test/resources/servicebus-compose.yaml
@@ -0,0 +1,30 @@
+services:
+ servicebus:
+ image: mcr.microsoft.com/azure-messaging/servicebus-emulator:latest
+ pull_policy: always
+ volumes:
+ - "./Config.json:/ServiceBus_Emulator/ConfigFiles/Config.json"
+ ports:
+ - "5672"
+ environment:
+ SQL_SERVER: sqledge
+ MSSQL_SA_PASSWORD: A_Str0ng_Required_Password
+ ACCEPT_EULA: Y
+ depends_on:
+ - sqledge
+ networks:
+ sb-emulator:
+ aliases:
+ - "sb-emulator"
+ sqledge:
+ image: "mcr.microsoft.com/azure-sql-edge:latest"
+ networks:
+ sb-emulator:
+ aliases:
+ - "sqledge"
+ environment:
+ ACCEPT_EULA: Y
+ MSSQL_SA_PASSWORD: A_Str0ng_Required_Password
+
+networks:
+ sb-emulator:
diff --git a/storage/spring-cloud-azure-starter-storage-blob/storage-blob-sample/pom.xml b/storage/spring-cloud-azure-starter-storage-blob/storage-blob-sample/pom.xml
index 5412de0bf..29805b40e 100644
--- a/storage/spring-cloud-azure-starter-storage-blob/storage-blob-sample/pom.xml
+++ b/storage/spring-cloud-azure-starter-storage-blob/storage-blob-sample/pom.xml
@@ -21,7 +21,7 @@
17
17
- 7.0.0
+ 7.1.0-beta.1
@@ -45,6 +45,38 @@
org.springframework.boot
spring-boot-starter-web
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.springframework.boot
+ spring-boot-testcontainers
+ test
+
+
+ com.azure.spring
+ spring-cloud-azure-docker-compose
+ test
+
+
+ org.testcontainers
+ azure
+ 1.21.3
+ test
+
+
+ org.testcontainers
+ junit-jupiter
+ 1.21.3
+ test
+
+
+ com.azure.spring
+ spring-cloud-azure-testcontainers
+ test
+
diff --git a/storage/spring-cloud-azure-starter-storage-blob/storage-blob-sample/src/test/java/com/azure/spring/sample/storage/resource/AzureBlobResourceDockerComposeTest.java b/storage/spring-cloud-azure-starter-storage-blob/storage-blob-sample/src/test/java/com/azure/spring/sample/storage/resource/AzureBlobResourceDockerComposeTest.java
new file mode 100644
index 000000000..7a487a6ab
--- /dev/null
+++ b/storage/spring-cloud-azure-starter-storage-blob/storage-blob-sample/src/test/java/com/azure/spring/sample/storage/resource/AzureBlobResourceDockerComposeTest.java
@@ -0,0 +1,48 @@
+package com.azure.spring.sample.storage.resource;
+
+import com.azure.spring.cloud.autoconfigure.implementation.context.AzureGlobalPropertiesAutoConfiguration;
+import com.azure.spring.cloud.autoconfigure.implementation.storage.blob.AzureStorageBlobAutoConfiguration;
+import com.azure.spring.cloud.autoconfigure.implementation.storage.blob.AzureStorageBlobResourceAutoConfiguration;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.WritableResource;
+import org.springframework.util.StreamUtils;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@SpringBootTest(properties = {
+ "spring.docker.compose.skip.in-tests=false",
+ "spring.docker.compose.file=classpath:storage-compose.yaml",
+ "spring.docker.compose.stop.command=down"
+})
+public class AzureBlobResourceDockerComposeTest {
+
+ @Value("azure-blob://testcontainers/message.txt")
+ private Resource blobFile;
+
+ @Test
+ void test() throws IOException {
+ String originalContent = "Hello World!";
+ try (OutputStream os = ((WritableResource) this.blobFile).getOutputStream()) {
+ os.write(originalContent.getBytes());
+ }
+ String resultContent = StreamUtils.copyToString(this.blobFile.getInputStream(), Charset.defaultCharset());
+ assertThat(resultContent).isEqualTo(originalContent);
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ @ImportAutoConfiguration(classes = {
+ AzureGlobalPropertiesAutoConfiguration.class,
+ AzureStorageBlobAutoConfiguration.class,
+ AzureStorageBlobResourceAutoConfiguration.class})
+ static class Config {
+ }
+}
diff --git a/storage/spring-cloud-azure-starter-storage-blob/storage-blob-sample/src/test/java/com/azure/spring/sample/storage/resource/AzureBlobResourceTestContainerTest.java b/storage/spring-cloud-azure-starter-storage-blob/storage-blob-sample/src/test/java/com/azure/spring/sample/storage/resource/AzureBlobResourceTestContainerTest.java
new file mode 100644
index 000000000..6cecc73df
--- /dev/null
+++ b/storage/spring-cloud-azure-starter-storage-blob/storage-blob-sample/src/test/java/com/azure/spring/sample/storage/resource/AzureBlobResourceTestContainerTest.java
@@ -0,0 +1,52 @@
+package com.azure.spring.sample.storage.resource;
+
+import com.azure.spring.cloud.autoconfigure.implementation.context.AzureGlobalPropertiesAutoConfiguration;
+import com.azure.spring.cloud.autoconfigure.implementation.storage.blob.AzureStorageBlobAutoConfiguration;
+import com.azure.spring.cloud.autoconfigure.implementation.storage.blob.AzureStorageBlobResourceAutoConfiguration;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
+import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.WritableResource;
+import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
+import org.springframework.util.StreamUtils;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@SpringJUnitConfig
+@Testcontainers
+class AzureBlobResourceTestContainerTest {
+ @Container
+ @ServiceConnection
+ private static final GenericContainer> AZURITE_CONTAINER = new GenericContainer<>(
+ "mcr.microsoft.com/azure-storage/azurite:latest")
+ .withExposedPorts(10000)
+ .withCommand("azurite --skipApiVersionCheck && azurite -l /data --blobHost 0.0.0.0 --queueHost 0.0.0.0 --tableHost 0.0.0.0");
+
+ @Value("azure-blob://testcontainers/message.txt")
+ private Resource blobFile;
+
+ @Test
+ void test() throws IOException {
+ String originalContent = "Hello World!";
+ try (OutputStream os = ((WritableResource) this.blobFile).getOutputStream()) {
+ os.write(originalContent.getBytes());
+ }
+ String resultContent = StreamUtils.copyToString(this.blobFile.getInputStream(), Charset.defaultCharset());
+ assertThat(resultContent).isEqualTo(originalContent);
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ @ImportAutoConfiguration(classes = {AzureGlobalPropertiesAutoConfiguration.class, AzureStorageBlobAutoConfiguration.class, AzureStorageBlobResourceAutoConfiguration.class})
+ static class Config {
+ }
+}
diff --git a/storage/spring-cloud-azure-starter-storage-blob/storage-blob-sample/src/test/resources/application.yml b/storage/spring-cloud-azure-starter-storage-blob/storage-blob-sample/src/test/resources/application.yml
new file mode 100644
index 000000000..854afc1c9
--- /dev/null
+++ b/storage/spring-cloud-azure-starter-storage-blob/storage-blob-sample/src/test/resources/application.yml
@@ -0,0 +1,10 @@
+# Storage account name length should be between 3 and 24 and use numbers and lower-case letters only
+# Configuration
+spring:
+ cloud:
+ azure:
+ storage:
+ blob:
+ account-name:
+ container-name:
+
diff --git a/storage/spring-cloud-azure-starter-storage-blob/storage-blob-sample/src/test/resources/storage-compose.yaml b/storage/spring-cloud-azure-starter-storage-blob/storage-blob-sample/src/test/resources/storage-compose.yaml
new file mode 100644
index 000000000..7a44d5069
--- /dev/null
+++ b/storage/spring-cloud-azure-starter-storage-blob/storage-blob-sample/src/test/resources/storage-compose.yaml
@@ -0,0 +1,7 @@
+services:
+ storage:
+ image: mcr.microsoft.com/azure-storage/azurite:latest
+ ports:
+ - '10000'
+ - '10001'
+ command: azurite -l /data --blobHost 0.0.0.0 --queueHost 0.0.0.0 --tableHost 0.0.0.0 --skipApiVersionCheck
\ No newline at end of file