skip to content

questions

4

How do you craft and inject a queue message the consumer cannot process for an automated test?

level: middleimportance: must knowfreq 52%

answer

  1. Enter through the real publishing path
  2. One nameable failure stage, not several
  3. Unique correlation identifier per run
  4. Undecodable body versus rejected value
  5. Baseline the parked count first

basics

~20 s

Publish the bad message through the same path a real producer uses, give it a payload that fails at exactly one nameable stage, and tag it with a unique identifier so the case can find it wherever it lands.

solid answer

~50 s

Send it the way production sends it: the same channel, the same publishing interface and the same envelope a real producer uses, so the case exercises the consumer's real entry point rather than a private method call. Choose a payload that fails at **one** stage you can name — a body that cannot be decoded, a required field missing, or a well-formed value no business rule accepts — because each fails in a different layer, and a message that could fail at several makes the assertion ambiguous. Give it a unique correlation identifier so every later check keys off that identifier instead of "the newest record". Build the payload inside the case rather than reading a shared file, give it its own routing key, and snapshot the parked count first so leftovers from an earlier run cannot pass the case for you.

code

pseudocode · 13 lines
pseudocode
marker = "poison-" + random_id()

bad = envelope(
    routing_key: marker,                        # its own key: do not stall a shared one
    metadata:    { correlation_id: marker, type: "order.placed" },
    body:        bytes("{not-a-valid-structure")  # fails at decode, nowhere else
)

baseline = parked_count()
publish(channel: "orders.incoming", message: bad)   # the path a real producer uses

await_until(deadline: 45s) { parked_record(correlation_id: marker) != null }
assert parked_count() == baseline + 1

go deeper

for a junior

Be ready to say what an unprocessable message is: one the consumer can never succeed on, however often it is delivered, unlike a message that failed only because a dependency was briefly unavailable.

for a middle

Explain why the message has to enter through the real publishing path, and why a payload that fails at exactly one stage — decoding, contract check or business rule — is what makes the assertion mean something.

for a senior

Show how you keep a deliberate failure safe in a shared environment: a fresh correlation identifier, its own routing key, a parked-count baseline, a bounded wait and clean-up, so your injected defect never becomes somebody else's flaky run.

for a principal

Own the position that the unprocessable path is designed behaviour with an owner, not an accident. Agree per consumer which stage rejects what, who is told, and that no consumer reaches production without a case that drives this path.

## What "unprocessable" actually means A queued message is **unprocessable** when the consumer can never succeed on it, however many times it is delivered. That is a different animal from a message that failed because a dependency was briefly unavailable: the second one will succeed on a later attempt, the first one never will. The behaviour under test is that the system tells the two apart — it keeps trying the transient failure and stops trying the permanent one, moving it aside so the rest of the traffic keeps flowing. Your case exists to prove that the permanent branch is real, and the first design decision is which flavour of "permanent" you are injecting. A consumer runs every incoming message through a chain of stages, and "cannot process" can happen at any of them. | Stage | What a bad message looks like there | What parking it proves | | --- | --- | --- | | Decode | bytes that do not form the agreed structure at all | the reader survives input it cannot even parse | | Contract check | readable, but a required field is missing or wrongly typed | validation runs ahead of business logic and rejects cleanly | | Business rule | complete and well-typed, but a value no rule accepts | a rejection is parked rather than crashing the reader | | Permanent refusal downstream | valid input a dependency will always reject | permanent refusals are separated from outages | **Pick exactly one stage per case.** A payload that is at once undecodable, missing a field and business-invalid gets rejected by whichever stage runs first; the case cannot say which one did the rejecting, and it will keep passing long after the stage you meant to cover has been deleted. One defect, one stage, one case — and a case name that says which. ## Inject it the way production would - **Publish through the real path.** Same channel, same publishing interface, same routing and credentials a real producer uses. Decoding and the error handling around it usually live in the machinery surrounding your handler, not inside it, so anything that bypasses that machinery skips the code you are testing. - **Do not call the handler directly with a bad object.** A direct call can only show that the handler throws. It cannot show that redelivery stopped, that an attempt ceiling was honoured, or that a record reached the parking destination with a reason attached. - **Do not hand-place a record in the parking destination.** That fakes the very outcome the case is supposed to observe, and it will keep passing on the day the consumer stops parking anything at all. - **Keep the envelope realistic.** Set the metadata a real producer sets — correlation fields, type markers, timestamps — because handlers often read those before they touch the body. A message that fails on a missing header when you meant to test a malformed body is a different case from the one you named. ## Make it findable, and safe beside other traffic 1. **Generate a unique correlation identifier per run** and put it in the metadata, and in the body where the body is readable at all. Every later assertion — parked record, log line, counter, replay — keys off that identifier rather than off "the most recent record", which stops being reliable the moment two runs overlap. 2. **Give the injected message its own routing key** where the transport preserves order for messages sharing one. A poison message can hold up everything queued behind it on the same key; unless that stalling is the thing under test, do not aim it at a key other cases depend on. 3. **Build the payload in the case, not from a shared file.** Two suites reading the same checked-in bad-message file will each assert on the other's records. 4. **Snapshot the parked count before injecting**, so a leftover from an earlier run can neither satisfy nor mask the assertion. 5. **Clean up afterwards.** Remove or mark the record your case parked; otherwise the parking destination fills with deliberate failures and the alert watching it becomes noise everybody learns to ignore. ## What this case is not It is not a resilience drill: nothing is taken down, no degraded behaviour is expected, and the dependency is healthy — only the input is wrong. It is not a proof that processing is idempotent. And it is not a test of the transport's own delivery promises; you are testing what your consumer does with input it can never accept. Keeping the case that narrow is exactly what makes its failure message worth reading: when it turns red, one behaviour has changed and the case name says which stage was supposed to reject the message.

  • Why publish the bad message instead of calling the consumer's handler directly with it?
    A direct call skips everything the case exists to test: the decoding the surrounding machinery performs, the error path that counts attempts, and the write into the parking destination. It can prove the handler throws and nothing more — not that redelivery stopped, and not that the message ended up somewhere an operator can find it.
  • How do you stop an injected unprocessable message from disturbing other cases running at the same time?
    Generate a fresh correlation identifier per run and assert only on records carrying it, so an old parked record cannot satisfy the assertion. Give the message its own routing key where the transport keeps order per key, so a stalled key does not hold up other traffic. Then delete or mark the parked record when the case finishes.
  • Which kind of unprocessable payload would you choose to cover the business-rule stage rather than decoding?
    One that is perfectly readable and structurally complete but carries a value no rule will ever accept — a quantity of minus three, a currency the catalogue does not sell in, an identifier for an entity that was permanently deleted. If it decodes and validates cleanly, the only thing left to reject it is the rule you meant to cover.

It is the deliberately mislabelled parcel a sorting centre puts through its own belts: it has to enter by the normal door, and it has to be unmistakable in the reject bin afterwards.

saying these in an interview costs you the question

  • Calls the handler directly and calls it an end-to-end check
  • Writes a record straight into the parking destination
  • Uses a payload that could fail at three different stages
  • Reuses one fixed bad-message file across parallel runs
  • Asserts only that the consumer process stayed alive
open as a page

How does a test prove an unprocessable queue message was parked rather than redelivered forever?

level: middleimportance: should knowfreq 44%

basics

~20 s

Parking leaves evidence that endless redelivery cannot fake: a record in the parking destination carrying this run's correlation identifier, a processing-attempt count that stops growing between two readings, and a later good message that gets processed.

open as a page

A queue consumer parks messages it cannot process without alerting anyone. What must an automated case assert so the park is not read as success?

level: seniorimportance: should knowfreq 36%

basics

~20 s

Assert three facts: redelivery stopped at the declared attempt ceiling, the parked record carries a reason, an attempt count and the correlation identifier, and the park moved an operator-visible signal. An unannounced park is contained failure, not success.

open as a page

After a fix, a parked queue message is resubmitted for processing. What must the case check beyond the effect appearing once?

level: seniorimportance: nice to knowfreq 22%

basics

~20 s

Check what the earlier failed attempts left behind, and whether the resubmitted message is now stale: partial work must not double the totals, newer state must not be overwritten, and the parked copy must not stay replayable.

open as a page