{"id":23130697,"url":"https://github.com/wellle/rmq","last_synced_at":"2026-03-13T23:01:42.034Z","repository":{"id":37664255,"uuid":"41258877","full_name":"wellle/rmq","owner":"wellle","description":"Message queue system written in Go and backed by Redis","archived":false,"fork":false,"pushed_at":"2024-12-13T13:54:57.000Z","size":1759,"stargazers_count":1616,"open_issues_count":3,"forks_count":208,"subscribers_count":81,"default_branch":"master","last_synced_at":"2026-03-11T01:46:08.059Z","etag":null,"topics":["go","golang","message-queue","redis"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wellle.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-08-23T17:08:08.000Z","updated_at":"2026-03-10T18:26:28.000Z","dependencies_parsed_at":"2023-02-12T12:45:57.699Z","dependency_job_id":"39b7bc9a-2ca1-4b68-8864-99edaf6291b3","html_url":"https://github.com/wellle/rmq","commit_stats":{"total_commits":279,"total_committers":36,"mean_commits":7.75,"dds":"0.31899641577060933","last_synced_commit":"e754f089648321bdc9398a362fdc99940ffafb35"},"previous_names":["wellle/rmq","adjust/rmq"],"tags_count":17,"template":false,"template_full_name":null,"purl":"pkg:github/wellle/rmq","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wellle%2Frmq","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wellle%2Frmq/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wellle%2Frmq/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wellle%2Frmq/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wellle","download_url":"https://codeload.github.com/wellle/rmq/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wellle%2Frmq/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30478926,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-13T20:45:58.186Z","status":"ssl_error","status_checked_at":"2026-03-13T20:45:20.133Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["go","golang","message-queue","redis"],"created_at":"2024-12-17T11:02:57.438Z","updated_at":"2026-03-13T23:01:41.545Z","avatar_url":"https://github.com/wellle.png","language":"Go","funding_links":[],"categories":["Projects","Go"],"sub_categories":["Message Broker"],"readme":"[![Build Status](https://github.com/adjust/rmq/workflows/test/badge.svg)](https://github.com/adjust/rmq/actions?query=branch%3Amaster+workflow%3Atest)\n[![GoDoc](https://pkg.go.dev/badge/github.com/adjust/rmq)](https://pkg.go.dev/github.com/adjust/rmq)\n\n## Overview\n\nrmq is short for Redis message queue. It's a message queue system written in Go\nand backed by Redis.\n\n## Basic Usage\n\nLet's take a look at how to use rmq.\n\n### Import\n\nOf course you need to import rmq wherever you want to use it.\n\n```go\nimport \"github.com/adjust/rmq/v5\"\n```\n\n### Connection\n\nBefore we get to queues, we first need to establish a connection. Each rmq\nconnection has a name (used in statistics) and Redis connection details\nincluding which Redis database to use. The most basic Redis connection uses a\nTCP connection to a given host and a port:\n\n```go\nconnection, err := rmq.OpenConnection(\"my service\", \"tcp\", \"localhost:6379\", 1, errChan)\n```\n\nIt's also possible to access a Redis listening on a Unix socket:\n\n```go\nconnection, err := rmq.OpenConnection(\"my service\", \"unix\", \"/tmp/redis.sock\", 1, errChan)\n```\n\nFor more flexible setup you can pass Redis options or create your own Redis client:\n\n```go\nconnection, err := OpenConnectionWithRedisOptions(\"my service\", redisOptions, errChan)\n```\n\n```go\nconnection, err := OpenConnectionWithRedisClient(\"my service\", redisClient, errChan)\n```\n\nIf the Redis instance can't be reached you will receive an error indicating this.\n\nPlease also note the `errChan` parameter. There is some rmq logic running in\nthe background which can run into Redis errors. If you pass an error channel to\nthe `OpenConnection()` functions rmq will send those background errors to this\nchannel so you can handle them asynchronously. For more details about this and\nhandling suggestions see the section about handling background errors below.\n\n#### Connecting to a Redis cluster\n\nIn order to connect to a Redis cluster please use `OpenClusterConnection()`:\n\n```go\nredisClusterOptions := \u0026redis.ClusterOptions{ /* ... */ }\nredisClusterClient := redis.NewClusterClient(redisClusterOptions)\nconnection, err := OpenClusterConnection(\"my service\", redisClusterClient, errChan)\n```\n\nNote that such an rmq cluster connection uses different Redis than rmq connections\nopened by `OpenConnection()` or similar. If you have used a Redis instance\nwith `OpenConnection()` then it is NOT SAFE to reuse that rmq system by connecting\nto it via `OpenClusterConnection()`. The cluster state won't be compatible and\nthis will likely lead to data loss.\n\nIf you've previously used `OpenConnection()` or similar you should only consider\nusing `OpenClusterConnection()` with a fresh Redis cluster.\n\n### Queues\n\nOnce we have a connection we can use it to finally access queues. Each queue\nmust have a unique name by which we address it. Queues are created once they\nare accessed. There is no need to declare them in advance. Here we open a queue\nnamed \"tasks\":\n\n```go\ntaskQueue, err := connection.OpenQueue(\"tasks\")\n```\n\nAgain, possibly Redis errors might be returned.\n\n### Producers\n\nAn empty queue is boring, let's add some deliveries! Internally all deliveries\nare saved to Redis lists as strings. This is how you can publish a string\npayload to a queue:\n\n```go\ndelivery := \"task payload\"\nerr := taskQueue.Publish(delivery)\n```\n\nIn practice, however, it's more common to have instances of some struct that we\nwant to publish to a queue. Assuming `task` is of some type like `Task`, this\nis how to publish the JSON representation of that task:\n\n```go\n// create task\ntaskBytes, err := json.Marshal(task)\nif err != nil {\n    // handle error\n}\n\nerr = taskQueue.PublishBytes(taskBytes)\n```\n\nFor a full example see [`example/producer`][producer.go].\n\n[producer.go]: example/producer/main.go\n\n### Consumers\n\nNow that our queue starts filling, let's add a consumer. After opening the\nqueue as before, we need it to start consuming before we can add consumers.\n\n```go\nerr := taskQueue.StartConsuming(10, time.Second)\n```\n\nThis sets the prefetch limit to 10 and the poll duration to one second. This\nmeans the queue will fetch up to 10 deliveries at a time before giving them to\nthe consumers. To avoid idling consumers while the queues are full, the\nprefetch limit should always be greater than the number of consumers you are\ngoing to add. If the queue gets empty, the poll duration sets how long rmq will\nwait before checking for new deliveries in Redis.\n\nOnce this is set up, we can actually add consumers to the consuming queue.\n\n```go\ntaskConsumer := \u0026TaskConsumer{}\nname, err := taskQueue.AddConsumer(\"task-consumer\", taskConsumer)\n```\n\nTo uniquely identify each consumer internally rmq creates a random name with\nthe given prefix. For example in this case `name` might be\n`task-consumer-WB1zaq`. This name is only used in statistics. \n\nIn our example above the injected `taskConsumer` (of type `*TaskConsumer`) must\nimplement the `rmq.Consumer` interface. For example:\n\n```go\nfunc (consumer *TaskConsumer) Consume(delivery rmq.Delivery) {\n    var task Task\n    if err = json.Unmarshal([]byte(delivery.Payload()), \u0026task); err != nil {\n        // handle json error\n        if err := delivery.Reject(); err != nil {\n            // handle reject error\n        }\n        return\n    }\n\n    // perform task\n    log.Printf(\"performing task %s\", task)\n    if err := delivery.Ack(); err != nil {\n        // handle ack error\n    }\n}\n```\n\nFirst we unmarshal the JSON package found in the delivery payload. If this\nfails we reject the delivery. Otherwise we perform the task and ack the\ndelivery.\n\nIf you don't actually need a consumer struct you can use `AddConsumerFunc`\ninstead and pass a consumer function which handles an `rmq.Delivery`:\n\n```go\nname, err := taskQueue.AddConsumerFunc(func(delivery rmq.Delivery) {\n    // handle delivery and call Ack() or Reject() on it\n})\n```\n\nPlease note that `delivery.Ack()` and similar functions have a built-in retry\nmechanism which will block your consumers in some cases. This is because\nfailing to acknowledge a delivery is potentially dangerous. For details\nsee the section about background errors below.\n\nFor a full example see [`example/consumer`][consumer.go].\n\n#### Consumer Lifecycle\n\nAs described above you can add consumers to a queue. For each consumer rmq\ntakes one of the prefetched unacked deliveries from the delivery channel and\npasses it to the consumer's `Consume()` function. The next delivery will only\nbe passed to the same consumer once the prior `Consume()` call returns. So each\nconsumer will only be consuming a single delivery at any given time.\n\nFurthermore each `Consume()` call is expected to call either `delivery.Ack()`,\n`delivery.Reject()` or `delivery.Push()` (see below). If that's not the case\nthese deliveries will remain unacked and the prefetch goroutine won't make\nprogress after a while. So make sure you always call exactly one of those\nfunctions in your `Consume()` implementations.\n\n[consumer.go]: example/consumer/main.go\n\n## Background Errors\n\nIt's recommended to inject an error channel into the `OpenConnection()`\nfunctions. This section describes it's purpose and how you might use it to\nmonitor rmq background Redis errors.\n\nThere are three sources of background errors which rmq detects (and handles\ninternally):\n\n1. The `OpenConnection()` functions spawn a goroutine which keeps a heartbeat\n   Redis key alive. This is important so that the cleaner (see below) can tell\n   which connections are still alive and must not be cleaned yet. If the\n   heartbeat goroutine fails to update the heartbeat Redis key repeatedly foo\n   too long the cleaner might clean up the connection prematurely. To avoid\n   this the connection will automatically stop all consumers after 45\n   consecutive heartbeat errors. This magic number is based on the details of\n   the heartbeat key: The heartbeat tries to update the key every second with a\n   TTL of one minute. So only after 60 failed attempts the heartbeat key would\n   be dead.\n\n   Every time this goroutine runs into a Redis error it gets send to the error\n   channel as `HeartbeatError`.\n\n2. The `StartConsuming()` function spawns a goroutine which is responsible for\n   prefetching deliveries from the Redis `ready` list and moving them into a\n   delivery channel. This delivery channels feeds into your consumers\n   `Consume()` functions. If the prefetch goroutine runs into Redis errors this\n   basically means that there won't be new deliveries being sent to your\n   consumers until it can fetch new ones. So these Redis errors are not\n   dangerous, it just means that your consumers will start idling until the\n   Redis connection recovers.\n\n   Every time this goroutine runs into a Redis error it gets send to the error\n   channel as `ConsumeError`.\n\n3. The delivery functions `Ack()`, `Reject()` and `Push()` have a built-in\n   retry mechanism. This is because failing to acknowledge a delivery\n   is potentially dangerous. The consumer has already handled the delivery, so\n   if it can't ack it the cleaner might end up moving it back to the ready list\n   so another consumer might end up consuming it again in the future, leading\n   to double delivery.\n\n   So if a delivery failed to be acked because of a Redis error the `Ack()`\n   call will block and retry once a second until it either succeeds or until\n   consuming gets stopped (see below). In the latter case the `Ack()` call will\n   return `rmq.ErrorConsumingStopped` which you should handle in your consume\n   function.  For example you might want to log about the delivery so you can\n   manually remove it from the unacked or ready list before you start new\n   consumers. Or at least you can know which deliveries might end up being\n   consumed twice.\n\n   Every time these functions runs into a Redis error it gets send to the error\n   channel as `DeliveryError`.\n\nEach of those error types has a field `Count` which tells you how often the\noperation failed consecutively. This indicates for how long the affected Redis\ninstance has been unavailable. One general way of using this information might\nbe to have metrics about the error types including the error count so you can\nkeep track of how stable your Redis instances and connections are. By\nmonitoring this you might learn about instabilities before they affect your\nservices in significant ways.\n\nBelow is some more specific advice on handling the different error cases\noutlined above. Keep in mind though that all of those errors are likely to\nhappen at the same time, as Redis tends to be up or down completely. But if\nyou're using multi Redis instance setup like [nutcracker][nutcracker] you might\nsee some of them in isolation from the others.\n\n1. `HeartbeatErrors`: Once `err.Count` equals `HeartbeatErrorLimit` you should\n   know that the consumers of this connection will stop consuming. And they\n   won't restart consuming on their own. This is a condition you should closely\n   monitor because this means you will have to restart your service in order to\n   resume consuming. Before restarting you should check your Redis instance.\n\n2. `ConsumeError`: These are mostly informational. As long as those errors keep\n   happening the consumers will effectively be paused. But once these\n   operations start succeeding again the consumers will resume consumers on\n   their own.\n\n3. `DeliveryError`: When you see deliveries failing to ack repeatedly this also\n   means your consumers won't make progress as they will keep retrying to ack\n   pending deliveries before starting to consume new ones. As long as this\n   keeps happening you should avoid stopping the service if you can. That is\n   because the already consumed by not yet unacked deliveries will be returned\n   to `ready` be the cleaner afterwards, which leads to double delivery. So\n   ideally you try to get Redis connection up again as long as the deliveries\n   are still trying to ack. Once acking works again it's safe to restart again.\n\n   More realistically, if you still need to stop the service when Redis is\n   down, keep in mind that calling `StopConsuming()` will make the blocking\n   `Ack()` calls return with `ErrorConsumingStopped`, so you can handle that\n   case to make an attempt to either avoid the double delivery or at least\n   track it for future investigation.\n\n[nutcracker]: https://github.com/twitter/twemproxy\n\n## Advanced Usage\n\n### Batch Consumers\n\nSometimes it's useful to have consumers work on batches of deliveries instead\nof individual ones. For example for bulk database inserts. In those cases you\ncan use `AddBatchConsumer()`:\n\n```go\nbatchConsumer := \u0026MyBatchConsumer{}\nname, err := taskQueue.AddBatchConsumer(\"my-consumer\", 100, time.Second, batchConsumer)\n```\n\nIn this example we create a batch consumer which will receive batches of up to\n100 deliveries. We set the `batchTimeout` to one second, so if there are less\nthan 100 deliveries per second we will still consume at least one batch per\nsecond (which would contain less than 100 deliveries).\n\nThe `rmq.BatchConsumer` interface is very similar to `rmq.Consumer`.\n\n```go\nfunc (consumer *MyBatchConsumer) Consume(batch rmq.Deliveries) {\n    payloads := batch.Payloads()\n    // handle payloads\n    if errors := batch.Ack(); len(errors) \u003e 0 {\n        // handle ack errors\n    }\n}\n```\n\nNote that `batch.Ack()` acknowledges all deliveries in the batch. It's also\npossible to ack some of the deliveries and reject the rest. It uses the same\nretry mechanism per delivery as discussed above. If some of the deliveries\ncontinue to fail to ack when consuming gets stopped (see below), then\n`batch.Ack()` will return an error map `map[int]error`. For each entry in this\nmap the key will be the index of the delivery which failed to ack and the value\nwill be the error it ran into. That way you can map the errors back to the\ndeliveries to know which deliveries are at risk of being consumed again in the\nfuture as discussed above.\n\nFor a full example see [`example/batch_consumer`][batch_consumer.go].\n\n[batch_consumer.go]: example/batch_consumer/main.go\n\n### Push Queues\n\nAnother thing which can be useful is a mechanism for retries. Let's say you\nhave tasks which can fail for external reasons but you'd like to retry them a\nfew times after a while before you give up. In that case you can set up a chain\nof push queues like this:\n\n```\nincomingQ -\u003e pushQ1 -\u003e pushQ2\n```\n\nIn the queue setup code it would look like this (error handling omitted for\nbrevity):\n\n```go\nincomingQ, err := connection.OpenQueue(\"incomingQ\")\npushQ1, err := connection.OpenQueue(\"pushQ1\")\npushQ2, err := connection.OpenQueue(\"pushQ2\")\nincomingQ.SetPushQueue(pushQ1)\npushQ1.SetPushQueue(pushQ2)\n_, err := incomingQ.AddConsumer(\"incomingQ\", NewConsumer())\n_, err := pushQ1.AddConsumer(\"pushQ1\", NewConsumer())\n_, err := pushQ2.AddConsumer(\"pushQ2\", NewConsumer())\n```\n\nIf you have set up your queues like this, you can now call `delivery.Push()` in\nyour `Consume()` function to push the delivery from the consuming queue to the\nassociated push queue. So if consumption fails on `incomingQ`, then the\ndelivery would be moved to `pushQ1` and so on. If you have the consumers wait\nuntil the deliveries have a certain age you can use this pattern to retry after\ncertain durations.\n\nNote that `delivery.Push()` has the same affect as `delivery.Reject()` if the\nqueue has no push queue set up. So in our example above, if the delivery fails\nin the consumer on `pushQ2`, then the `Push()` call will reject the delivery.\n\n### Stop Consuming\n\nIf you want to stop consuming from the queue, you can call `StopConsuming()`:\n\n```go\nfinishedChan := taskQueue.StopConsuming()\n```\n\nWhen `StopConsuming()` is called, it will immediately stop fetching more\ndeliveries from Redis and won't send any more of the already prefetched\ndeliveries to consumers.\n\nIn the background it will make pending `Ack()` calls return\n`rmq.ErrorConsumingStopped` if they still run into Redis errors (see above) and\nwait for all consumers to finish consuming their current delivery before\nclosing the returned `finishedChan`. So while `StopConsuming()` returns\nimmediately, you can wait on the returned channel until all consumers are done:\n\n```go\n\u003c-finishedChan\n```\n\nYou can also stop consuming on all queues in your connection:\n\n```go\nfinishedChan := connection.StopAllConsuming()\n```\n\nWait on the `finishedChan` to wait for all consumers on all queues to finish.\n\nThis is useful to implement a graceful shutdown of a consumer service. Please\nnote that after calling `StopConsuming()` the queue might not be in a state\nwhere you can add consumers and call `StartConsuming()` again. If you have a\nuse case where you actually need that sort of flexibility, please let us know.\nCurrently for each queue you are only supposed to call `StartConsuming()` and\n`StopConsuming()` at most once.\n\nAlso note that `StopAllConsuming()` will stop the heartbeat for this connection.\nIt's advised to also not publish to any queue opened by this connection anymore.\n\n### Return Rejected Deliveries\n\nEven if you don't have a push queue setup there are cases where you need to\nconsume previously failed deliveries again. For example an external dependency\nmight have an issue or you might have deployed a broken consumer service which\nrejects all deliveries for some reason.\n\nIn those cases you would wait for the external party to recover or fix your\nmistake to get ready to reprocess the deliveries again. Now you can return the\ndeliveries by opening affected queue and call `ReturnRejected()`:\n\n```go\nreturned, err := queue.ReturnRejected(10000)\n```\n\nIn this case we ask rmq to return up to 10k deliveries from the `rejected` list\nto the `ready` list. To return all of them you can pass `math.MaxInt64`.\n\nIf there was no error it returns the number of deliveries that were moved.\n\nIf you find yourself doing this regularly on some queues consider setting up a\npush queue to automatically retry failed deliveries regularly.\n\nSee [`example/returner`][returner.go]\n\n[returner.go]: example/returner/main.go\n\n### Purge Rejected Deliveries\n\nYou might run into the case where you have rejected deliveries which you don't\nintend to retry again for one reason or another. In those cases you can clear\nthe full `rejected` list by calling `PurgeRejected()`:\n\n```go\ncount, err := queue.PurgeRejected()\n```\n\nIt returns the number of purged deliveries.\n\nSimilarly, there's a function to clear the `ready` list of deliveries:\n\n```go\ncount, err := queue.PurgeReady()\n```\n\nSee [`example/purger`][purger.go].\n\n[purger.go]: example/purger/main.go\n\n### Cleaner\n\nYou should regularly run a queue cleaner to make sure no unacked deliveries are\nstuck in the queue system. The background is that a consumer service prefetches\ndeliveries by moving them from the `ready` list to an `unacked` list associated\nwith the queue connection. If the consumer dies by crashing or even by being\ngracefully shut down by calling `StopConsuming()`, the unacked deliveries will\nremain in that Redis list.\n\nIf you run a queue cleaner regularly it will detect queue connections whose\nheartbeat expired and will clean up all their consumer queues by moving their\nunacked deliveries back to the `ready` list.\n\nAlthough it should be safe to run multiple cleaners, it's recommended to run\nexactly one instance per queue system and have it trigger the cleaning process\nregularly, like once a minute.\n\nSee [`example/cleaner`][cleaner.go].\n\n[cleaner.go]: example/cleaner/main.go\n\n### Header\n\nRedis protocol does not define a specific way to pass additional data like header.\nHowever, there is often need to pass them (for example for traces propagation).\n\nThis implementation injects optional header values marked with a signature into \npayload body during publishing. When message is consumed, if signature is present, \nheader and original payload are extracted from augmented payload.\n\nHeader is defined as `http.Header` for better interoperability with existing libraries,\nfor example with [`propagation.HeaderCarrier`](https://pkg.go.dev/go.opentelemetry.io/otel/propagation#HeaderCarrier).\n\n```go\n // ....\n \n h := make(http.Header)\n h.Set(\"X-Baz\", \"quux\")\n\n // You can add header to your payload during publish.\n _ = pub.Publish(rmq.PayloadWithHeader(`{\"foo\":\"bar\"}`, h))\n\n // ....\n\n _, _ = con.AddConsumerFunc(\"tag\", func(delivery rmq.Delivery) {\n     // And receive header back in consumer.\n     delivery.(rmq.WithHeader).Header().Get(\"X-Baz\") // \"quux\"\n     \n     // ....\n })\n```\n\nAdding a header is an explicit opt-in operation and so it does not affect library's\nbackwards compatibility by default (when not used). \n\nPlease note that adding header may lead to compatibility issues if:\n* consumer is built with older version of `rmq` when publisher has already \n   started using header, this can be avoided by upgrading consumers before publishers;\n* consumer is not using `rmq` (other libs, low level tools like `redis-cli`) and is \n   not aware of payload format extension.\n\n## Testing Included\n\nTo simplify testing of queue producers and consumers we include test mocks.\n\n### Test Connection\n\nAs before, we first need a queue connection, but this time we use a\n`rmq.TestConnection` that doesn't need any connection settings.\n\n```go\ntestConn := rmq.NewTestConnection()\n```\n\nIf you are using a testing framework that uses test suites, you can reuse that\ntest connection by setting it up once for the suite and resetting it with\n`testConn.Reset()` before each test.\n\n### Producer Tests\n\nNow let's say we want to test the function `publishTask()` that creates a task\nand publishes it to a queue from that connection.\n\n```go\n// call the function that should publish a task\npublishTask(testConn)\n\n// check that the task is published\nassert.Equal(t, \"task payload\", suite.testConn.GetDelivery(\"tasks\", 0))\n```\n\nThe `assert.Equal` part is from [testify][testify], but it will look similar\nfor other testing frameworks. Given a `rmq.TestConnection`, we can check the\ndeliveries that were published to its queues (since the last `Reset()` call)\nwith `GetDelivery(queueName, index)`. In this case we want to extract the first\n(and possibly only) delivery that was published to queue `tasks` and just check\nthe payload string.\n\nIf the payload is JSON again, the unmarshalling and check might look like this:\n\n```go\nvar task Task\nerr := json.Unmarshal([]byte(suite.testConn.GetDelivery(\"tasks\", 0)), \u0026task)\nassert.NoError(t, err)\nassert.NotNil(t, task)\nassert.Equal(t, \"value\", task.Property)\n```\n\nIf you expect a producer to create multiple deliveries you can use different\nindexes to access them all.\n\n```go\nassert.Equal(t, \"task1\", suite.testConn.GetDelivery(\"tasks\", 0))\nassert.Equal(t, \"task2\", suite.testConn.GetDelivery(\"tasks\", 1))\n```\n\nFor convenience there's also a function `GetDeliveries` that returns all\npublished deliveries to a queue as string array.\n\n```go\nassert.Equal(t, []string{\"task1\", \"task2\"}, suite.testConn.GetDeliveries(\"tasks\"))\n```\n\nThese examples assume that you inject the `rmq.Connection` into your testable\nfunctions. If you inject instances of `rmq.Queue` instead, you can use\n`rmq.TestQueue` instances in tests and access their `LastDeliveries` (since\n`Reset()`) directly.\n\n[testify]: https://github.com/stretchr/testify\n\n### Consumer Tests\n\nTesting consumers is a bit easier because consumers must implement the\n`rmq.Consumer` interface. In the tests just create an `rmq.TestDelivery` and\npass it to your `Consume()` function. This example creates a test delivery from\na string and then checks that the delivery was acked.\n\n```go\nconsumer := \u0026TaskConsumer{}\ndelivery := rmq.NewTestDeliveryString(\"task payload\")\n\nconsumer.Consume(delivery)\n\nassert.Equal(t, rmq.Acked, delivery.State)\n```\n\nThe `State` field will always be one of these values:\n\n- `rmq.Acked`: The delivery was acked\n- `rmq.Rejected`: The delivery was rejected\n- `rmq.Pushed`: The delivery was pushed (see below)\n- `rmq.Unacked`: Nothing of the above\n\nIf your packages are JSON marshalled objects, then you can create test\ndeliveries out of those like this:\n\n```go\ntask := Task{Property: \"bad value\"}\ndelivery := rmq.NewTestDelivery(task)\n```\n\n### Integration Tests\n\nIf you want to write integration tests which exercise both producers and\nconsumers at the same time, you can use the\n`rmq.OpenConnectionWithTestRedisClient` constructor. It returns a real\n`rmq.Connection` instance which is backed by an in-memory Redis client\nimplementation. That way it behaves exactly as in production, just without the\ndurability of a real Redis client. Don't use this in production!\n\n## Statistics\n\nGiven a connection, you can call `connection.CollectStats()` to receive\n`rmq.Stats` about all open queues, connections and consumers. If you run\n[`example/handler`][handler.go] you can see what's available:\n\n\u003cimg width=\"610\" src=\"https://user-images.githubusercontent.com/474504/82765106-1c53a600-9e14-11ea-8c30-e96821afa0d8.png\"\u003e\n\nIn this example you see 5 connections consuming `task_kind1`, each with 5\nconsumers. They have a total of 1007 packages unacked. Below the marker you see\nconnections which are not consuming. One of the handler connections died\nbecause I stopped the handler. Running the cleaner would clean that up (see\nbelow).\n\n[handler.go]: example/handler/main.go\n\n### Prometheus\n\nIf you are using Prometheus, [rmqprom](https://github.com/pffreitas/rmqprom)\ncollects statistics about all open queues and exposes them as Prometheus\nmetrics.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwellle%2Frmq","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwellle%2Frmq","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwellle%2Frmq/lists"}