> Use when writing unit tests, integration tests, creating test fixtures, or running tests with bench run-tests. Prevents flaky tests from missing fixtures, incorrect test isolation, and wrong test base classes. Covers frappe.tests.utils, IntegrationTestCase, UnitTestCase, test fixtures, bench run-tests flags, test naming conventions.
npx skills add https://github.com/Impertio-Studio/Frappe_Claude_Skill_Package --skill frappe-testing-unit
| Task | Command / Class |
|------|----------------|
| Run all tests | bench --site test_site run-tests |
| Run tests for app | bench --site test_site run-tests --app myapp |
| Run tests for doctype | bench --site test_site run-tests --doctype "Sales Order" |
| Run single test method | bench --site test_site run-tests --doctype "Sales Order" --test test_submit |
| Run tests for module | bench --site test_site run-tests --module "myapp.mymodule.doctype.mydt.test_mydt" |
| Run with profiler | bench --site test_site run-tests --doctype "Task" --profile |
| Run with failfast | bench --site test_site run-tests --failfast |
| Generate JUnit XML | bench --site test_site run-tests --junit-xml-output /path/report.xml |
| Skip fixture loading | bench --site test_site run-tests --skip-test-records --skip-before-tests |
| Base class (v14) | from frappe.tests.utils import FrappeTestCase |
| Unit test class (v15+) | from frappe.tests.classes import UnitTestCase |
| Integration test class (v15+) | from frappe.tests.classes import IntegrationTestCase |
Need to test a function or method in isolation?
├─ YES → Does it require database access?
│ ├─ NO → UnitTestCase (v15+) or FrappeTestCase (v14)
│ └─ YES → IntegrationTestCase (v15+) or FrappeTestCase (v14)
└─ NO → Need to test document lifecycle (create/submit/cancel)?
├─ YES → IntegrationTestCase (v15+) or FrappeTestCase (v14)
└─ NO → Need to test permissions or user context?
├─ YES → IntegrationTestCase (v15+) or FrappeTestCase (v14)
└─ NO → UnitTestCase (v15+) or FrappeTestCase (v14)
Version note: In v14, FrappeTestCase is the ONLY base class. In v15+, it still works (deprecated wrapper) but ALWAYS prefer UnitTestCase or IntegrationTestCase for new code.
from frappe.tests.utils import FrappeTestCase
class TestMyDoctype(FrappeTestCase):
def test_something(self):
doc = frappe.get_doc({"doctype": "My Doctype", "field": "value"})
doc.insert()
self.assertEqual(doc.field, "value")
Behavior: Resets frappe.local.flags after each test. Database transactions start before each test and rollback afterward. ALWAYS call super().setUpClass() if you override setUpClass.
from frappe.tests.classes import UnitTestCase
class TestMyUtils(UnitTestCase):
def test_calculation(self):
result = my_calculation(10, 20)
self.assertEqual(result, 30)
def test_html_output(self):
html = generate_html()
self.assertEqual(self.normalize_html(html), self.normalize_html(expected))
Behavior: Sets frappe.set_user("Administrator") in setUpClass. Auto-detects doctype from module path. Provides normalize_html(), normalize_sql(), assertDocumentEqual(), assertQueryEqual(), assertSequenceSubset().
from frappe.tests.classes import IntegrationTestCase
class TestSalesOrder(IntegrationTestCase):
def test_submit_order(self):
so = frappe.get_doc({
"doctype": "Sales Order",
"customer": "_Test Customer",
"items": [{"item_code": "_Test Item", "qty": 1, "rate": 100}]
}).insert()
so.submit()
self.assertEqual(so.docstatus, 1)
Behavior: Extends UnitTestCase. Calls frappe.init() and sets up site connection. Loads test record dependencies via make_test_records(). Provides primary_connection() and secondary_connection() context managers. maxDiff = 10_000.
ALWAYS place test files in the doctype directory following this naming convention:
myapp/
└── mymodule/
└── doctype/
└── my_doctype/
├── my_doctype.py # DocType controller
├── my_doctype.json # DocType definition
├── test_my_doctype.py # Test file (MUST start with test_)
└── test_records.json # Optional: test fixtures
Rules:
test_ — the test runner ignores files without this prefixtest_{doctype_in_snake_case}.py for doctype teststest_*.py namingCreate a test_records.json file in the doctype directory:
[
{
"doctype": "My Doctype",
"field1": "_Test Value 1",
"field2": 100
},
{
"doctype": "My Doctype",
"field1": "_Test Value 2",
"field2": 200
}
]
Rules:
_Test to distinguish from production data_test_records = [
{"doctype": "My Doctype", "field1": "_Test Value 1"},
{"doctype": "My Doctype", "field1": "_Test Value 2"},
]
def create_test_data():
if frappe.flags.test_data_created:
return
frappe.set_user("Administrator")
frappe.get_doc({
"doctype": "My Doctype",
"field1": "_Test Value",
}).insert()
frappe.flags.test_data_created = True
class TestMyDoctype(IntegrationTestCase):
def setUp(self):
create_test_data()
ALWAYS use frappe.flags to prevent duplicate fixture creation across test methods.
class TestInvoice(IntegrationTestCase):
def test_full_lifecycle(self):
# Create
doc = frappe.get_doc({"doctype": "Sales Invoice", ...}).insert()
self.assertEqual(doc.docstatus, 0) # Draft
# Submit
doc.submit()
self.assertEqual(doc.docstatus, 1) # Submitted
# Cancel
doc.cancel()
self.assertEqual(doc.docstatus, 2) # Cancelled
class TestPermissions(IntegrationTestCase):
def test_user_cannot_read_private(self):
frappe.set_user("[email protected]")
doc = frappe.get_doc("Event", {"subject": "_Test Private Event"})
self.assertFalse(frappe.has_permission("Event", doc=doc))
def tearDown(self):
# ALWAYS reset user in tearDown
frappe.set_user("Administrator")
class TestAccess(IntegrationTestCase):
def test_restricted_access(self):
with self.set_user("[email protected]"):
self.assertRaises(
frappe.PermissionError,
frappe.get_doc, "Salary Slip", "SAL-001"
)
# User automatically restored after context manager exits
class TestAPI(IntegrationTestCase):
def test_whitelisted_method(self):
frappe.set_user("[email protected]")
result = frappe.call("myapp.api.get_dashboard_data", filters={})
self.assertIsInstance(result, dict)
self.assertIn("total", result)
from unittest.mock import patch, MagicMock
class TestIntegration(IntegrationTestCase):
@patch("myapp.integrations.stripe.requests.post")
def test_payment_gateway(self, mock_post):
mock_post.return_value = MagicMock(
status_code=200,
json=lambda: {"status": "success", "id": "ch_123"}
)
result = process_payment(amount=1000, currency="USD")
self.assertEqual(result["status"], "success")
mock_post.assert_called_once()
class TestFeature(IntegrationTestCase):
def test_with_modified_settings(self):
with self.change_settings("Selling Settings", {"so_required": 1}):
# Settings temporarily changed
self.assertRaises(frappe.ValidationError, create_delivery_note)
# Settings automatically reverted
class TestHooks(IntegrationTestCase):
def test_custom_hook(self):
with self.patch_hooks({"on_submit": ["myapp.hooks.custom_on_submit"]}):
doc = create_and_submit_doc()
# Verify hook was executed
| Context Manager | Available On | Purpose |
|----------------|-------------|---------|
| set_user(user) | UnitTestCase, IntegrationTestCase | Temporarily switch user context |
| change_settings(dt, **kw) | UnitTestCase, IntegrationTestCase | Temporarily modify settings |
| patch_hooks(overrides) | UnitTestCase, IntegrationTestCase | Temporarily override hooks |
| freeze_time(time) | UnitTestCase, IntegrationTestCase | Freeze time for deterministic tests |
| debug_on(*exceptions) | UnitTestCase, IntegrationTestCase | Drop into debugger on exception |
| timeout(seconds) | Decorator | Fail test if it exceeds time limit |
| enable_safe_exec() | IntegrationTestCase | Enable server scripts temporarily |
| switch_site(site) | IntegrationTestCase | Switch to a different site |
| assertQueryCount(n) | IntegrationTestCase | Assert exact SQL query count |
| assertRedisCallCounts(**kw) | IntegrationTestCase | Assert Redis command counts |
| assertRowsRead(n) | IntegrationTestCase | Assert row-level DB access limits |
frappe.db callssetUp and rollback in tearDownfrappe.db.commit() in tests — this breaks test isolationfrappe.flags.in_test to check if code is running under the test runnerif frappe.flags.in_test:
# Skip external API calls, emails, etc.
return mock_response()
NEVER use frappe.flags.in_test to skip validation logic — tests MUST exercise the same code paths as production.
super().setUpClass() — omitting this breaks fixture loading and user setupfrappe.db.commit() in tests — this persists data across tests and breaks isolationtearDown if you called frappe.set_user() directly (v14 pattern)_Test — makes cleanup and identification easyfrappe.flags to guard fixture creation — prevents duplicate insertsToolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.
Use when implementation is complete, all tests pass, and you need to decide how to integrate the work - guides completion of development work by presenting structured options for merge, PR, or cleanup
Use when implementing any feature or bugfix, before writing implementation code
Use when encountering any bug, test failure, or unexpected behavior, before proposing fixes
Use when about to claim work is complete, fixed, or passing, before committing or creating PRs - requires running verification commands and confirming output before making any success claims; evidence before assertions always
Expert guidance for systematic backtesting of trading strategies. Use when developing, testing, stress-testing, or validating quantitative trading strategies. Covers "beating ideas to death" methodology, parameter robustness testing, slippage modeling, bias prevention, and interpreting backtest results. Applicable when user asks about backtesting, strategy validation, robustness testing, avoiding overfitting, or systematic trading development.
Cloud laboratory platform for automated protein testing and validation. Use when designing proteins and needing experimental validation including binding assays, expression testing, thermostability measurements, enzyme activity assays, or protein sequence optimization. Also use for submitting experiments via API, tracking experiment status, downloading results, optimizing protein sequences for better expression using computational tools (NetSolP, SoluProt, SolubleMPNN, ESM), or managing protein design workflows with wet-lab validation.
This skill should be used for time series machine learning tasks including classification, regression, clustering, forecasting, anomaly detection, segmentation, and similarity search. Use when working with temporal data, sequential patterns, or time-indexed observations requiring specialized algorithms beyond standard ML approaches. Particularly suited for univariate and multivariate time series analysis with scikit-learn compatible APIs.
Take impertio-studio/frappe-testing-unit from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.