testing-patterns
How to write robust Frappe tests — FrappeTestCase, factories, mocking frappe.sendmail / external HTTP, permission tests, change_settings, anti-patterns to avoid. Use when writing or reviewing test_*.py files, designing test fixtures, or thinking about test isolation. The /frappe-test command runs tests; this skill teaches how to write them.
What this skill does
# Frappe Testing Patterns
Reference for writing maintainable tests in Frappe v14+. Focused on the patterns that hold up as your test suite grows past a few dozen tests — factory functions, fixture isolation, mocking, and the anti-patterns that cause flaky failures in CI.
## Which base class
```python
from frappe.tests.utils import FrappeTestCase
```
Use `FrappeTestCase` (not `unittest.TestCase`) for anything that touches Frappe — controllers, APIs, DB. It handles:
- Per-test transaction rollback (you don't need to clean up created docs in `tearDown`)
- `frappe.flags.in_test = True` (so production code can branch on test mode if needed)
- Test record loading from `test_records.json`
- Permission isolation (resets `frappe.session.user` between tests)
Use plain `unittest.TestCase` only for pure-Python helpers with no Frappe touchpoints (string utilities, math, etc.).
## File location and naming
```
my_app/
└── my_module/
└── doctype/
└── expense_claim/
├── expense_claim.py
├── expense_claim.js
├── expense_claim.json
├── test_expense_claim.py ← convention
└── test_records.json ← optional: seed data for the suite
```
Frappe discovers tests by walking each app's `<module>/doctype/<doctype>/test_*.py`. For non-DocType tests (utilities, integrations), put them in `my_app/tests/` and Frappe still finds them.
Test methods must start with `test_`. Helper methods on the test class (factories, common assertions) should not — name them `_make_invoice`, `_assert_balanced`, etc.
## Test data lifecycle
### `test_records.json` — suite-level fixtures
```json
[
{
"doctype": "Customer",
"customer_name": "_Test Customer Alpha",
"customer_type": "Company",
"territory": "_Test Territory"
},
{
"doctype": "Item",
"item_code": "_Test Item Widget",
"item_name": "Widget",
"stock_uom": "Nos"
}
]
```
Loaded once per test run, available to every test in the file via `frappe.get_doc("Customer", "_Test Customer Alpha")`. Use the `_Test` prefix on names — it's the team convention so test data is visually distinct from production seed data.
`test_records.json` is loaded with `ignore_permissions=True`, so it can create docs the test user doesn't normally have access to.
### `setUp` vs `setUpClass`
```python
class TestExpenseClaim(FrappeTestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
# Runs ONCE per test class. Use for read-only setup that won't be mutated.
cls.approver_user = "[email protected]"
cls.create_approver_if_missing()
def setUp(self):
# Runs before EACH test. Use for state that tests will mutate.
# Don't put expensive setup here.
frappe.set_user("Administrator")
def tearDown(self):
# Often unneeded — FrappeTestCase rolls back the transaction automatically.
# Use only for things that escape the transaction (file writes, real HTTP).
pass
```
Most tests need no `setUp` or `tearDown` at all — the per-test rollback handles it.
### Factory pattern
For docs that vary per test, write a `_make_<thing>` helper that takes overrides:
```python
class TestExpenseClaim(FrappeTestCase):
def _make_expense_claim(self, **overrides):
defaults = {
"doctype": "Expense Claim",
"employee": "_Test Employee Alpha",
"expenses": [{
"expense_type": "Travel",
"amount": 100.0,
"expense_date": frappe.utils.nowdate(),
}],
}
defaults.update(overrides)
doc = frappe.get_doc(defaults)
doc.insert()
return doc
def test_total_calculation(self):
claim = self._make_expense_claim(expenses=[
{"expense_type": "Travel", "amount": 100.0, "expense_date": frappe.utils.nowdate()},
{"expense_type": "Food", "amount": 50.0, "expense_date": frappe.utils.nowdate()},
])
self.assertEqual(claim.total_claimed_amount, 150.0)
def test_default_status(self):
claim = self._make_expense_claim()
self.assertEqual(claim.status, "Draft")
```
Factories should be **idempotent on the parts that don't change between tests**. The Customer and Item are seed data; only the Expense Claim is created per test.
## Permission tests
```python
def test_employee_cannot_approve_own_claim(self):
# Set the user we're testing as
frappe.set_user("[email protected]") # Employee role
claim = self._make_expense_claim(employee="HR-EMP-Alice")
# Try the privileged operation
with self.assertRaises(frappe.PermissionError):
from frappe.model.workflow import apply_workflow
apply_workflow(claim, "Approve")
# Reset before the next test (FrappeTestCase auto-resets, but explicit is safer)
frappe.set_user("Administrator")
def test_manager_can_approve(self):
self._make_employee("[email protected]", roles=["Employee"])
self._make_employee("[email protected]", roles=["Employee", "Expense Approver"])
frappe.set_user("[email protected]")
claim = self._make_expense_claim()
apply_workflow(claim, "Submit")
frappe.set_user("[email protected]")
apply_workflow(claim, "Approve")
self.assertEqual(claim.workflow_state, "Approved")
```
Always reset to `Administrator` (or `Guest` for unauthenticated tests) explicitly — even though `FrappeTestCase` does it for you, it makes the intent obvious and protects against test ordering changes.
## Mocking external calls
Use `unittest.mock.patch` from the standard library. Frappe ships no special mocking framework.
### Mock `frappe.sendmail`
Most tests should not actually send mail. Patch it for the duration of the test:
```python
from unittest.mock import patch
class TestExpenseClaim(FrappeTestCase):
@patch("frappe.sendmail")
def test_approval_sends_notification(self, mock_sendmail):
claim = self._make_expense_claim()
apply_workflow(claim, "Approve")
mock_sendmail.assert_called_once()
call_kwargs = mock_sendmail.call_args.kwargs
self.assertIn("approved", call_kwargs["subject"].lower())
self.assertEqual(call_kwargs["recipients"], [claim.employee_email])
```
### Mock external HTTP
```python
@patch("requests.post")
def test_webhook_fires_on_submit(self, mock_post):
mock_post.return_value.status_code = 200
mock_post.return_value.json.return_value = {"received": True}
claim = self._make_expense_claim()
claim.submit()
mock_post.assert_called_once()
self.assertEqual(mock_post.call_args.args[0], "https://hooks.example.com/expense")
```
### Mock `frappe.publish_realtime`
```python
@patch("frappe.publish_realtime")
def test_progress_published_during_long_job(self, mock_publish):
run_my_long_job(args)
self.assertTrue(mock_publish.called)
# Inspect call_args_list for ordering
```
## `frappe.flags.in_test`
Set automatically by `FrappeTestCase`. Use it sparingly in production code:
```python
def send_notification(recipient, message):
if frappe.flags.in_test:
# Skip real send during tests — but tests should mock frappe.sendmail directly anyway
return
frappe.sendmail(recipients=[recipient], message=message)
```
Prefer mocking the function in the test over branching on `in_test` in production code. Branching pollutes business logic with test concerns; mocking keeps the production path clean.
## `change_settings` context manager
For tests that depend on a Single doctype's setting:
```python
from frappe.tests.utils import change_settings
class TestStockNegative(FrappeTestCase):
def test_negative_stock_blocked(self):
# Default: should not allow negative stock
with self.assertRaises(frappe.ValidationError):
self._make_delivery_note_exceeding_stock()
def test_negative_stock_allowed_when_setting_on(selfRelated in Writing & Docs
jax-development
IncludedUse this skill when the user is writing, debugging, profiling, refactoring, reviewing, benchmarking, parallelising, exporting, or explaining JAX code, or when they mention JAX, jax.numpy, jit, grad, value_and_grad, vmap, scan, lax, random keys, pytrees, jax.Array, sharding, Mesh, PartitionSpec, NamedSharding, pmap, shard_map, Pallas, XLA, StableHLO, checkify, profiler, or the JAX repo. It helps turn NumPy or PyTorch-style code into pure functional JAX, fix tracer/control-flow/shape/PRNG bugs, remove recompiles and host-device syncs, choose transforms and sharding strategies, inspect jaxpr/lowering/IR, and benchmark compiled code correctly.
nature-article-writer
IncludedDrafts, rewrites, diagnostically critiques, and style-calibrates primary research manuscripts for Nature and Nature Portfolio journals. Use when the user wants a Nature-style title, summary paragraph or abstract, introduction, results, discussion, methods, figure legends, presubmission enquiry, cover letter, reviewer response, or when a scientific draft sounds generic, jargon-heavy, structurally weak, or AI-ish and needs precise, broad-reader-friendly prose without inventing data, analyses, or references. Best for primary research articles and letters rather than reviews or press releases unless explicitly adapting one.
deckrd
IncludedDocument-driven framework that derives requirements, specifications, implementation plans, and executable tasks from goals through structured AI dialogue. Use when user says "write requirements", "create spec", "plan implementation", "derive tasks", "structure this feature", "break down into tasks", or "document this module". Also use for reverse engineering existing code into docs (/deckrd rev). Do NOT use for direct code writing — use /deckrd-coder after tasks are generated. Do NOT use when the user only wants to run or fix existing code without planning.
clinical-decision-support
IncludedGenerate professional clinical decision support (CDS) documents for pharmaceutical and clinical research settings, including patient cohort analyses (biomarker-stratified with outcomes) and treatment recommendation reports (evidence-based guidelines with decision algorithms). Supports GRADE evidence grading, statistical analysis (hazard ratios, survival curves, waterfall plots), biomarker integration, and regulatory compliance. Outputs publication-ready LaTeX/PDF format optimized for drug development, clinical research, and evidence synthesis.
handling-sf-data
IncludedSalesforce data operations with 130-point scoring. Use this skill to create, update, delete, bulk import/export, generate test data, and clean up org records using sf CLI and anonymous Apex. TRIGGER when: user creates test data, performs bulk import/export, uses sf data CLI commands, needs data factory patterns for Apex tests, or needs to seed/clean records in a Salesforce org. DO NOT TRIGGER when: SOQL query writing only (use querying-soql), Apex test execution (use running-apex-tests), or metadata deployment (use deploying-metadata).
accelint-ac-to-playwright
IncludedConvert and validate acceptance criteria for Playwright test automation. Use when user asks to (1) review/evaluate/check if AC are ready for automation, (2) assess if AC can be converted as-is, (3) validate AC quality for Playwright, (4) turn AC into tests, (5) generate tests from acceptance criteria, (6) convert .md bullets or .feature Gherkin files to Playwright specs, (7) create test automation from requirements. Handles both bullet-style markdown and Gherkin syntax with JSON test plan generation and validation.