Skip to content

RabbitMQ

dev.rightsize.modules.RabbitMQContainer — a single-node RabbitMQ container with the management plugin enabled. Defaults to the image's own guest/guest credential pair.

Defaults

Default image rabbitmq:4-management-alpine
Exposed ports 5672 (AMQP), 15672 (management UI/API)
Env RABBITMQ_DEFAULT_USER=guest, RABBITMQ_DEFAULT_PASS=guest
Wait strategy Wait.forLogMessage(".*Server startup complete.*", times = 1)

Helpers

Member Returns
amqpUrl: String An amqp:// URL (with embedded credentials) for the running container's AMQP listener
managementUrl: String The management UI/API base URI
username / password: String The configured management/AMQP credentials (default guest/guest)
withUsername(username: String): RabbitMQContainer Overrides RABBITMQ_DEFAULT_USER
withPassword(password: String): RabbitMQContainer Overrides RABBITMQ_DEFAULT_PASS

Call the withX overrides before start().

Example

package dev.rightsize.modules

import com.rabbitmq.client.ConnectionFactory
import com.rabbitmq.client.GetResponse
import dev.rightsize.modules.RabbitMQContainer
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Test

class RabbitMQContainerTest {
    @Test
    fun `publishes and receives a message`() {
        val rabbit = RabbitMQContainer()
        rabbit.start()
        try {
            val factory = ConnectionFactory()
            factory.setUri(rabbit.amqpUrl)
            factory.newConnection().use { conn ->
                conn.createChannel().use { ch ->
                    // RabbitMQ 4.x rejects non-durable, non-exclusive queues by default —
                    // see "A 4.x behavior change" below.
                    ch.queueDeclare("q1", true, false, false, null)
                    ch.basicPublish("", "q1", null, "hello".toByteArray())

                    var delivery: GetResponse? = null
                    val deadline = System.currentTimeMillis() + 5000
                    while (delivery == null && System.currentTimeMillis() < deadline) {
                        delivery = ch.basicGet("q1", true)
                        if (delivery == null) Thread.sleep(100)
                    }
                    assertNotNull(delivery, "never received the published message")
                    assertEquals("hello", String(delivery!!.body))
                }
            }
        } finally {
            rabbit.stop()
        }
    }
}

Note the small poll loop around basicGet — a fresh publish can briefly race the broker's own enqueue, so a single immediate basicGet isn't guaranteed to see it yet.

Backend notes

Readiness is unambiguous — no double-boot to worry about. Unlike the SQL modules, RabbitMQ's "Server startup complete" line (unchanged from the 3.x series into 4.x) appears exactly once per boot, so times = 1 (the default) is correct with no false-match trap to anchor against. No memory-limit override is needed either — an Erlang VM, not a JVM, boots comfortably under microsandbox's default ~450 MB microVM RAM (observed ~5.5s round-trip on both backends in this module's own integration test).

A RabbitMQ 4.x behavior change that bites naive client code (not this module's concern, but worth knowing). RabbitMQ 4.x deprecates transient_nonexcl_queues, and — per the broker's own startup warning — a client that declares a non-durable, non-exclusive queue (durable=false, exclusive=false) can be rejected outright with reply-code=541 INTERNAL_ERROR, reproduced directly against this module's pinned image. Declare durable, non-exclusive queues (or exclusive transient ones) from your own test/client code — the example above does exactly that (queueDeclare("q1", true, false, false, null)).