Blog / Practical notes for builders

Test the awkward cases before a bot release

Test the awkward cases before a bot release; BadAssBots.com is for sale

A bot release needs tests for the moments when the system does not behave like the demo. The network can disappear after an action succeeds. A provider can deliver an event again. An account can lose permission between the time a command is displayed and the time someone clicks it. These cases are easier to reason about before users encounter them.

Build a small acceptance set around one meaningful action. For this guide, use a fictional bot that creates a project from an approved request. A correct result is one project with the right owner and a recorded link to the source request. A clear failure is a visible unresolved request with enough information for a person to recover it. A cheerful message with no project is a defect.

State the business assertion first

Write the expected outcome in ordinary language before choosing the testing framework. “The handler returns 200” is a protocol assertion. “One approved request creates exactly one project” is the business assertion. You may need both, but the second is what exposes duplicate or missing actions.

Define which records the test will inspect. Check the source request, destination project, job state, and user-visible message where applicable. Give each test a unique identifier so its records are easy to locate and remove. Keep test accounts separate from production accounts, and use fictional data that does not require copying a customer's private information.

Save the fixture and expectation with the application code. Include the relevant API version or adapter version and any environment setup needed to run it. A test that works only on one developer's laptop without explanation will be difficult to use as a release gate.

Send the same input more than once

Deliver the same event twice in sequence. Then send it concurrently from two workers. The expected business result should remain one project for one approved request. Inspect the stored state after both deliveries finish. A log message claiming that a duplicate was ignored is not enough if the second project was already created.

Stripe's webhook documentation describes duplicate event handling, including recording processed event identifiers. Treat that as a provider-specific contract to understand, then test your own implementation. A simple check followed by a later insert may still allow two workers to pass the check together unless the storage operation enforces uniqueness appropriately.

Also test distinct events that refer to the same intended action. Deduplicating only the delivery identifier may not express your business rule. If two notifications both mean “create the project for request 42,” the workflow needs a stable identity for that action. Write down when a new request represents a new action and when it represents a retry.

Simulate an unknown outcome

The most revealing timeout occurs after the destination commits the action but before your worker receives its response. Arrange a test double or controlled test endpoint that creates the project and then drops the connection. The bot now knows that its request was sent but does not know whether it succeeded.

Test how it resolves that uncertainty. If the provider supports idempotency keys for the operation, reuse the key for the same intended action according to that provider's rules. Stripe's idempotent request documentation explains one implementation, including reuse of the stored result and parameter checks. Do not assume a different API implements the same behavior.

If the destination has no suitable idempotency mechanism, consider whether a stable external reference allows reconciliation before retry. Some actions cannot be safely inferred from a search result. In that case, stop automatic repetition and create a review item with the uncertain status. An honest unresolved state is preferable to silently multiplying effects.

Interrupt the worker at each boundary

Stop execution after receiving the event, after storing the job, after starting the external action, and after recording the result. Restart the worker and inspect what happens. Each boundary tests a different gap between your database and the external system. A single crash test near the start does not cover all of them.

The recovered job should have a clear path: safe retry, confirmed completion, or human review. Avoid leaving a permanent “running” record because the original process never returned. Define how stale work is detected and who can inspect it. A recovery control should show the original action identity so an operator does not create a fresh duplicate by clicking retry.

Test the recovery path with two workers too. If both can claim the same abandoned job, your restart procedure may introduce the very duplication the normal handler prevents. Verify the actual stored outcomes under concurrency rather than relying only on a diagram of the intended flow.

Change permissions and payload shape

Revoke the destination permission and run the normal request. Expect a clear error category, preserved source data, and no success message. Restore access and verify the documented recovery action. The operator should not need to edit arbitrary database rows to resume an ordinary failed job.

Remove a required field, change a field's type, and add an unfamiliar optional field. The handler should reject or route invalid inputs deliberately while tolerating harmless additions where the schema permits them. Test empty strings separately from missing values if the distinction affects business rules. Include unexpected text in user-controlled fields to check that it remains data.

Verify authentication failures at the boundary as well. Invalid signatures, wrong credentials, or an event addressed to a different installation should not enter the normal action path. Keep error output useful without printing secrets or full sensitive payloads. A release test can inspect the log artifact for fields that should never appear there.

Check timing and user-visible state

Delay the destination response beyond the normal processing time. The bot should acknowledge or defer work according to its platform's contract, and eventually report a recorded result. Discord's interaction documentation specifies timing for initial responses; a Discord bot's tests should reflect that requirement instead of relying on a generic web-server timeout.

Test whether a user who clicks again sees the existing operation or starts another one. Disable or update stale controls when appropriate, but enforce the business rule on the server too. A disabled button improves the interface; it cannot prevent a second request from another device or a replayed event.

Inspect failure wording. “Try again” is unsuitable when the first action may have completed. Use a message that accurately reflects the state and offers a safe route to check it. The test should compare both the saved outcome and the message, so a future wording change does not hide an unresolved operation.

Make the release evidence small and repeatable

Run the case set against the version you intend to ship and save the result with that version identifier. Record which tests use local doubles and which contact a provider's test environment. Doubles are useful for difficult timing scenarios, but they do not prove that credentials, platform configuration, or live schemas are correct.

Where the provider offers delivery inspection, use it for a focused integration check. GitHub's webhook redelivery guide describes how to repeat deliveries for testing or recovery. Match the observed delivery with your application's record and the resulting action.

Start with a dozen purposeful cases rather than a sprawling checklist nobody maintains. Include normal success, repeated input, concurrent input, unknown outcome, interrupted work, invalid data, revoked access, and truthful user feedback. Add a case when a new failure teaches you something. The release question is whether the bot reaches the right state under these conditions, and whether a person can recover when it cannot.

Michael Santiago

About Michael Santiago

Michael founded i-Newswire.com in 2007, growing through iNewswire.com into the category-defining Newswire.com. The business sold to Issuer Direct for $44 million in 2022. That experience informs his belief in clear, memorable names. Today he develops premium domains and companies through OnlineBusiness.com.