---
title: "Testing"
space: "Commera"
url: "https://docs.bwh.tech/commera/plugin-reference/testing"
updated: "2026-10-09"
---

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.

```python
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.

:::caution[Rollback is per test class]

`IntegrationTestCase` rolls back after the whole class, not after each test. Rows from one test are still there in the next. Use a unique value per test for any unique field, such as an AWB. Error Log rows survive the rollback.

:::

## Code that commits

A handler or method that calls `frappe.db.commit()` writes past the test rollback. Patch the commit in the test:

```python
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.

```python
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.

```python
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

1. Place an order on the storefront.
2. In `/commera`, open **Settings > Plugins** and click **Open** next to your plugin. **Recent deliveries** shows each delivery and its status. [See deliveries](/commera/plugin-reference/events) lists the statuses.
3. If a delivery failed, open **Error Log** in Desk. The title is `commera_events["<event>"] hook failed: <handler>`.
4. Fix the code. Then, as a System Manager, click **Retry** on the Failed delivery.

To read the rows from `bench --site <site> console`:

```python
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.

## Next steps

- [Events](/commera/plugin-reference/events)
- [Hooks](/commera/plugin-reference/hooks)
- [Gotchas](/commera/plugin-reference/gotchas)
