Test your plugin at three levels: call the handler directly, run the real delivery queue in a test, and check the rows on a development site.
Call the handler directly
Build a CommeraEvent and call your handler. Run it as the plugin user, the same user that runs real deliveries.
import frappe
from frappe.tests import IntegrationTestCase
from frappe.utils import now_datetime
from commera.sdk.events import CommeraEvent
from my_app.orders import on_order_paid
def make_order_event(sales_order: str, name: str, data: dict | None = None) -> CommeraEvent:
return CommeraEvent(
id=f"{sales_order}-{name}",
name=name,
reference_doctype="Sales Order",
reference_name=sales_order,
data=data or {},
created_at=now_datetime(),
)
class TestOrders(IntegrationTestCase):
def test_a_repeated_paid_event_makes_one_job(self):
event = make_order_event(self.sales_order, "order_paid")
frappe.set_user("commera-plugins@commera.local")
self.addCleanup(frappe.set_user, "Administrator")
on_order_paid(event)
on_order_paid(event)
self.assertEqual(frappe.db.count("My Job", {"event_id": event.id}), 1)
Call the handler twice in one test. Delivery is at least once, so a second call must do nothing.
Code that commits
A handler or method that calls frappe.db.commit() writes past the test rollback. Patch the commit in the test:
from unittest.mock import patch
import frappe
class TestSendOrder(IntegrationTestCase):
def setUp(self):
commit_patch = patch.object(frappe.db, "commit")
commit_patch.start()
self.addCleanup(commit_patch.stop)
Fake the cart for a hook
A hook reads quotation.items and fields such as quotation.grand_total. Use types.SimpleNamespace for a fake cart. frappe._dict(items=[...]) does not work, because .items is the dict method.
from types import SimpleNamespace
from my_app.cart import get_cart_refusal
cart = SimpleNamespace(name="test-cart", items=[SimpleNamespace(item_code="TSHIRT-RED-M")])
self.assertIsNone(get_cart_refusal(cart))
If your hook calls quotation.get(...), build the fake with frappe._dict for the fields and a separate attribute for the lines, or test against a real Quotation.
Run the real delivery queue
To test the whole path, run Commera's background jobs inline. Do not patch frappe.enqueue away: Commera queues each delivery with it.
from unittest.mock import patch
import frappe
def run_enqueued_jobs_now(test_case):
enqueue = frappe.enqueue
def enqueue_now(method, *args, **kwargs):
kwargs.update(now=True, enqueue_after_commit=False)
return enqueue(method, *args, **kwargs)
for enqueue_patch in (
patch.object(frappe, "enqueue", side_effect=enqueue_now),
patch.object(frappe.db, "commit"),
patch.object(frappe.db, "rollback"),
):
enqueue_patch.start()
test_case.addCleanup(enqueue_patch.stop)
Call it in setUp, make a store order, and check that your handler's record exists. A real order needs Order Type Shopping Cart, or no order event fires.
On a development site
- Place an order on the storefront.
- In
/commera, open Settings > Plugins and click Open next to your plugin. Recent deliveries shows each delivery and its status. See deliveries lists the statuses. - If a delivery failed, open Error Log in Desk. The title is
commera_events["<event>"] hook failed: <handler>. - Fix the code. Then, as a System Manager, click Retry on the Failed delivery.
To read the rows from bench --site <site> console:
frappe.get_all("Commera Event", filters={"reference_name": "SAL-ORD-2026-00062"}, fields=["name", "event"])
frappe.get_all(
"Commera Event Delivery",
filters={"parent": "SAL-ORD-2026-00062-order_paid"},
fields=["app", "handler", "status"],
)
Make sure that the scheduler and a worker run on the site. Retries wait for the scheduler, and every delivery runs in a worker.