Why this matters
Some objects are hard to test because they are tightly coupled to the environment: a GUI View requires a running UI framework, a database trigger requires a live database. The Humble Object pattern solves this by splitting such objects into two parts: the humble part that is hard to test (the View, the trigger), and the testable part that is easy to test (the Presenter, the logic).
The idea: move ALL logic into the testable part. The Presenter computes every string, every boolean flag, every sort order, every formatted date, and every conditional label before the View even runs. The View receives a ViewModel that contains only pre-formatted strings and booleans — it renders them directly, with zero conditional logic of its own.
The result: the Presenter can be exhaustively unit-tested as a pure function — no browser, no jsdom, no template engine. The View is so simple it cannot have a bug. If a shipping threshold changes from $100 to $150, you update the Presenter and the test — never the template.
The problem
Templates and components with conditional logic — shipping thresholds, date formatting, status colors — all untestable without rendering the full UI.
Bad
# order_detail.html (Jinja2 template with logic):
#
# {% if order.total > 100 %}
# <span class="badge green">Free Shipping</span>
# {% else %}
# <span class="badge red">+ $9.99 Shipping</span>
# {% endif %}
#
# <span class="status {{ 'green' if order.status == 'paid' else 'red' }}">
# {{ order.status | upper }}
# </span>
# <p>Ordered on: {{ order.created_at.strftime('%d/%m/%Y') }}</p>
# <p>Total: ${{ "%.2f" % order.total }}</p>
#
# All logic — shipping threshold, status color, date/money format —
# is trapped in the template. You cannot unit-test it.
# A bug in the shipping threshold goes undetected until production.
// React component with business logic in JSX:
export function OrderDetail({ order }: { order: Order }) {
return (
<div>
<span className={`badge ${order.total > 100 ? "green" : "red"}`}>
{order.total > 100 ? "Free Shipping" : "+ $9.99 Shipping"}
</span>
<span className={`status ${order.status === "paid" ? "green" : "red"}`}>
{order.status.toUpperCase()}
</span>
<p>{new Date(order.createdAt).toLocaleDateString("en-GB")}</p>
</div>
);
}
// Testing requires React Testing Library, jsdom, and rendering.
// Logic hidden in JSX is easy to miss and hard to refactor.
The solution
The Presenter computes a ViewModel with pre-formatted strings and booleans. The template or component is humble — it only renders what it receives, with zero conditional logic.
Good
from dataclasses import dataclass
from decimal import Decimal
@dataclass
class OrderViewModel:
total_display: str # "$127.50"
shipping_label: str # "Free Shipping" or "+ $9.99 Shipping"
date_display: str # "15/03/2024"
status_label: str # "PAID"
status_color: str # "green" or "red"
class OrderPresenter:
THRESHOLD = Decimal("100.00")
def present(self, order) -> OrderViewModel:
free = order.total >= self.THRESHOLD
return OrderViewModel(
total_display = f"${order.total:,.2f}",
shipping_label = "Free Shipping" if free else "+ $9.99 Shipping",
date_display = order.created_at.strftime("%d/%m/%Y"),
status_label = order.status.upper(),
status_color = "green" if order.status == "paid" else "red",
)
# Template (humble — zero logic):
# <span class="{{ vm.status_color }}">{{ vm.shipping_label }}</span>
# <p>{{ vm.date_display }}</p>
# Unit test — no template rendering:
# vm = OrderPresenter().present(order)
# assert vm.shipping_label == "Free Shipping"
export interface OrderViewModel {
totalDisplay: string; // "$127.50"
shippingLabel: string; // "Free Shipping" | "+ $9.99 Shipping"
dateDisplay: string; // "15/03/2024"
statusLabel: string; // "PAID"
statusColor: string; // "green" | "red"
}
// Presenter — pure function, fully unit-testable with plain Jest:
export function presentOrder(order: Order): OrderViewModel {
const free = order.total > 100;
return {
totalDisplay: \`$\${order.total.toFixed(2)}\`,
shippingLabel: free ? "Free Shipping" : "+ $9.99 Shipping",
dateDisplay: new Date(order.createdAt).toLocaleDateString("en-GB"),
statusLabel: order.status.toUpperCase(),
statusColor: order.status === "paid" ? "green" : "red",
};
}
// Component (humble — just renders the ViewModel, no logic):
export function OrderDetail({ order }: { order: Order }) {
const vm = presentOrder(order);
return (
<div>
<span className={vm.statusColor}>{vm.shippingLabel}</span>
<p>{vm.dateDisplay}</p>
<p>{vm.totalDisplay}</p>
</div>
);
}
// Unit test — no React, no jsdom:
// expect(presentOrder(order).shippingLabel).toBe("Free Shipping");
Key takeaway
If your template has an if-statement, it has logic that isn't being tested. Move all conditional logic, formatting, and computed values to the Presenter. Make the View so simple it cannot have a bug — then test the Presenter exhaustively as a pure function, without any UI infrastructure.