diff --git a/docs/modules/azure.md b/docs/modules/azure.md
index 19c141c7639..2d26be1a8cb 100644
--- a/docs/modules/azure.md
+++ b/docs/modules/azure.md
@@ -5,12 +5,13 @@ This module is INCUBATING. While it is ready for use and operational in the curr
Testcontainers module for the Microsoft Azure's [SDK](https://github.com/Azure/azure-sdk-for-java).
-Currently, the module supports `Azurite` and `CosmosDB` emulators. In order to use them, you should use the following classes:
+Currently, the module supports `Azurite`, `CosmosDB`, and `Servicebus` emulators. In order to use them, you should use the following classes:
Class | Container Image
-|-
AzuriteContainer | [mcr.microsoft.com/azure-storage/azurite](https://github.com/microsoft/containerregistry)
CosmosDBEmulatorContainer | [mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator](https://github.com/microsoft/containerregistry)
+AzureServicebusEmulatorContainer | [mcr.microsoft.com/azure-messaging/servicebus-emulator](https://github.com/microsoft/containerregistry)
## Usage example
@@ -104,6 +105,33 @@ Test against the Emulator:
[Testing against Azure CosmosDB Emulator container](../../modules/azure/src/test/java/org/testcontainers/containers/CosmosDBEmulatorContainerTest.java) inside_block:testWithClientAgainstEmulatorContainer
+### Azure service bus Emulator
+
+Start Azure service bus Emulator during a test:
+
+
+[Starting a Azure Service bus Emulator container with custom config](../../modules/azure/src/test/java/org/testcontainers/azure/AzureServicebusEmulatorContainerTest.java) inside_block:emulatorContainerCustomConfig
+
+
+!!! note
+ This starts the service bus emulator with a custom config.
+ To use [default config](https://github.com/Azure/azure-service-bus-emulator-installer/blob/main/ServiceBus-Emulator/Config/Config.json)
+ omit `withConfigFile(...)`.
+
+!!! note
+ The service bus emulator requires a database, so a [MSSQLServerContainer](../../modules/mssqlserver/src/main/java/org/testcontainers/containers/MSSQLServerContainer.java)
+ is started.
+
+Build Azure Service bus sender client:
+
+
+[Testing against Azure Service bus Emulator container](../../modules/azure/src/test/java/org/testcontainers/azure/AzureServicebusEmulatorContainerTest.java) inside_block:buildClient
+
+
+* See [Overview of the Azure Service Bus emulator](https://learn.microsoft.com/en-us/azure/service-bus-messaging/overview-emulator) for features and limitations.
+* [Test locally by using the Azure Service Bus emulator](https://learn.microsoft.com/en-us/azure/service-bus-messaging/test-locally-with-service-bus-emulator?tabs=docker-linux-container)
+
+
## Adding this module to your project dependencies
Add the following dependency to your `pom.xml`/`build.gradle` file:
diff --git a/modules/azure/build.gradle b/modules/azure/build.gradle
index c6cfb6738d0..d2093f020f0 100644
--- a/modules/azure/build.gradle
+++ b/modules/azure/build.gradle
@@ -5,9 +5,13 @@ dependencies {
// TODO use JDK's HTTP client and/or Apache HttpClient5
shaded 'com.squareup.okhttp3:okhttp:4.12.0'
+ implementation project(':mssqlserver')
+ testImplementation 'com.microsoft.sqlserver:mssql-jdbc:12.8.1.jre8'
+
testImplementation 'org.assertj:assertj-core:3.26.3'
testImplementation 'com.azure:azure-cosmos:4.63.3'
testImplementation 'com.azure:azure-storage-blob:12.29.0'
testImplementation 'com.azure:azure-storage-queue:12.24.0'
testImplementation 'com.azure:azure-data-tables:12.5.0'
+ testImplementation 'com.azure:azure-messaging-servicebus:7.17.8'
}
diff --git a/modules/azure/src/main/java/org/testcontainers/azure/AzureServicebusEmulatorContainer.java b/modules/azure/src/main/java/org/testcontainers/azure/AzureServicebusEmulatorContainer.java
new file mode 100644
index 00000000000..5b8f1bb8159
--- /dev/null
+++ b/modules/azure/src/main/java/org/testcontainers/azure/AzureServicebusEmulatorContainer.java
@@ -0,0 +1,86 @@
+package org.testcontainers.azure;
+
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.MSSQLServerContainer;
+import org.testcontainers.containers.Network;
+import org.testcontainers.containers.wait.strategy.Wait;
+import org.testcontainers.utility.DockerImageName;
+import org.testcontainers.utility.MountableFile;
+
+/**
+ * Testcontainers implementation for Azure service bus emulator.
+ *
+ * Supported image: {@code mcr.microsoft.com/azure-messaging/servicebus-emulator}
+ *
+ * Exposed ports: 5672
+ *
+ * If the official client, azure-messaging-servicebus, is used the oldest version supported is 7.17.8.
+ *
+ *
+ * By default, emulator uses config.json configuration file.
+ * To supply your own config your own config using {@code withConfigFile(MountableFile)}
+ *
+ *
+ * The service bus emulator requires a database, so a {@code MSSQLServerContainer} is also started.
+ *
+ */
+public class AzureServicebusEmulatorContainer extends GenericContainer {
+ private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(
+ "mcr.microsoft.com/azure-messaging/servicebus-emulator"
+ );
+
+ private static final int PORT = 5672;
+
+ /**
+ * @param dockerImageName specified docker image name to run
+ */
+ public AzureServicebusEmulatorContainer(final DockerImageName dockerImageName) {
+ super(dockerImageName);
+ dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);
+ withExposedPorts(PORT);
+
+ waitingFor(Wait.forLogMessage(".*Emulator Service is Successfully Up!.*", 1));
+
+ withNetwork(Network.SHARED);
+ withNetworkAliases("sb-emulator");
+ MSSQLServerContainer mssqlServerContainer = new MSSQLServerContainer(DockerImageName.parse("mcr.microsoft.com/mssql/server:2022-CU14-ubuntu-22.04"))
+ .acceptLicense();
+ String mssqlNetworkAlias = "sqlserver";
+ dependsOn(
+ mssqlServerContainer
+ .withNetwork(Network.SHARED)
+ .withNetworkAliases(mssqlNetworkAlias)
+ );
+ addEnv("MSSQL_SA_PASSWORD", mssqlServerContainer.getPassword());
+ addEnv("SQL_SERVER", mssqlNetworkAlias);
+ acceptLicense();
+ }
+
+ /**
+ * @param configFile config.json
+ * @return this
+ */
+ public AzureServicebusEmulatorContainer withConfigFile(MountableFile configFile) {
+ return withCopyFileToContainer(
+ configFile,
+ "/ServiceBus_Emulator/ConfigFiles/Config.json"
+ );
+ }
+
+ /**
+ * Accepts the license for the Azure Service Bus Emulator container by setting the ACCEPT_EULA=Y
+ * variable as described at https://github.com/Azure/azure-service-bus-emulator-installer/blob/main/README.md#license
+ */
+ public AzureServicebusEmulatorContainer acceptLicense() {
+ addEnv("ACCEPT_EULA", "Y");
+ return self();
+ }
+
+ /**
+ * @return connection string for connecting to the service bus.
+ */
+ public String getConnectionString() {
+ Integer mappedPort = getMappedPort(5672);
+ return "Endpoint=sb://localhost:" + mappedPort + ";SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=SAS_KEY_VALUE;UseDevelopmentEmulator=true;";
+ }
+}
diff --git a/modules/azure/src/test/java/org/testcontainers/azure/AzureServicebusEmulatorContainerTest.java b/modules/azure/src/test/java/org/testcontainers/azure/AzureServicebusEmulatorContainerTest.java
new file mode 100644
index 00000000000..a51fd7331a1
--- /dev/null
+++ b/modules/azure/src/test/java/org/testcontainers/azure/AzureServicebusEmulatorContainerTest.java
@@ -0,0 +1,62 @@
+package org.testcontainers.azure;
+
+import com.azure.core.util.IterableStream;
+import com.azure.messaging.servicebus.ServiceBusClientBuilder;
+import com.azure.messaging.servicebus.ServiceBusMessage;
+import com.azure.messaging.servicebus.ServiceBusReceivedMessage;
+import com.azure.messaging.servicebus.ServiceBusReceiverClient;
+import com.azure.messaging.servicebus.ServiceBusSenderClient;
+import com.azure.messaging.servicebus.models.ServiceBusReceiveMode;
+import org.junit.Test;
+import org.testcontainers.utility.DockerImageName;
+import org.testcontainers.utility.MountableFile;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class AzureServicebusEmulatorContainerTest {
+
+ @Test
+ public void testWithCustomConfig() {
+ try(
+ // emulatorContainerCustomConfig {
+ AzureServicebusEmulatorContainer azureServicebusEmulatorContainer = new AzureServicebusEmulatorContainer(
+ DockerImageName.parse("mcr.microsoft.com/azure-messaging/servicebus-emulator")
+ ).withConfigFile(MountableFile.forClasspathResource("/servicebus-config.json"))
+ // }
+ ) {
+ azureServicebusEmulatorContainer.start();
+ sendAndReceive(azureServicebusEmulatorContainer.getConnectionString(), "our.queue");
+ }
+ }
+
+ private static void sendAndReceive(String connectionString, String queueName) {
+ List sentMessages = Arrays.asList("Hello World");
+ try (
+ // buildClient {
+ ServiceBusSenderClient sender = new ServiceBusClientBuilder()
+ .connectionString(connectionString)
+ .sender()
+ .queueName(queueName)
+ .buildClient()
+ // }
+ ) {
+ for (String m : sentMessages) {
+ sender.sendMessage(new ServiceBusMessage(m));
+ }
+ }
+ try (ServiceBusReceiverClient reciever = new ServiceBusClientBuilder()
+ .connectionString(connectionString)
+ .receiver()
+ .queueName(queueName)
+ .receiveMode(ServiceBusReceiveMode.RECEIVE_AND_DELETE)
+ .buildClient()) {
+ IterableStream messagesStream = reciever.receiveMessages(sentMessages.size());
+ List recievedMessages = messagesStream.stream().map(m -> m.getBody().toString()).collect(Collectors.toList());
+ assertThat(recievedMessages).isEqualTo(sentMessages);
+ }
+ }
+}
diff --git a/modules/azure/src/test/resources/servicebus-config.json b/modules/azure/src/test/resources/servicebus-config.json
new file mode 100644
index 00000000000..dbac910ad53
--- /dev/null
+++ b/modules/azure/src/test/resources/servicebus-config.json
@@ -0,0 +1,108 @@
+{
+ "UserConfig": {
+ "Namespaces": [
+ {
+ "Name": "sbemulatorns",
+ "Queues": [
+ {
+ "Name": "our.queue",
+ "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"
+ }
+ }
+}