mcpbeat Sign in

Frappe Testing Unit Agent Skill

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

10k tokens
context cost
the whole folder, loaded on every use
6
files
instructions only
0
copies elsewhere
how many repositories repackaged it
158
stars on the repo
on the repository, not the skill itself

Install

one command, takes just this skill from the repository
npx skills add https://github.com/Impertio-Studio/Frappe_Claude_Skill_Package --skill frappe-testing-unit

The instruction itself

25 sections, as written by the author

Unit & Integration Testing

Quick Reference

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

Decision Tree: Which Test Base Class?

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.

Test Base Classes

FrappeTestCase (v14: still works in v15+ as compatibility wrapper)

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.

UnitTestCase (v15+): No Database Access

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().

IntegrationTestCase (v15+): Full Database Access

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.

Test File Structure

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:

  • ALWAYS prefix test files with test_ — the test runner ignores files without this prefix
  • ALWAYS use test_{doctype_in_snake_case}.py for doctype tests
  • NEVER place test files outside the doctype directory for doctype-specific tests
  • Non-doctype tests can live in any module, but MUST follow the test_*.py naming

Test Fixtures

Method 1: test_records.json (Static Fixtures)

Create 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:

  • ALWAYS prefix test data values with _Test to distinguish from production data
  • The test runner auto-loads these before running tests for the doctype
  • Link field dependencies are resolved automatically — the runner builds records for linked DocTypes first

Method 2: _test_records List (In-Module Fixtures)

_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.

Testing Patterns

Testing Document Lifecycle

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

Testing Permissions

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")

Testing with User Context (v15+ Context Manager)

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

Testing Whitelisted Methods

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)

Mocking External Services

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()

Testing with Settings Changes

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

Testing with Hook Overrides

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 Managers Reference

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

Database State Management

  • IntegrationTestCase: ALWAYS rolls back database after each test — no cleanup needed
  • UnitTestCase: No database connection — NEVER use frappe.db calls
  • Each test gets a clean state: transactions start in setUp and rollback in tearDown
  • NEVER call frappe.db.commit() in tests — this breaks test isolation
  • Use frappe.flags.in_test to check if code is running under the test runner

Detecting Test Mode

if 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.

Common Pitfalls

  • NEVER forget super().setUpClass() — omitting this breaks fixture loading and user setup
  • NEVER call frappe.db.commit() in tests — this persists data across tests and breaks isolation
  • ALWAYS reset user in tearDown if you called frappe.set_user() directly (v14 pattern)
  • ALWAYS prefix test data with _Test — makes cleanup and identification easy
  • NEVER rely on test execution order — each test MUST be independent
  • ALWAYS use frappe.flags to guard fixture creation — prevents duplicate inserts

See Also

  • references/examples.md — Complete test examples
  • references/anti-patterns.md — Common mistakes and fixes
  • references/fixtures.md — Fixture patterns in depth
  • references/api-reference.md — Full API reference for test utilities
  • frappe-testing-cicd — CI/CD pipeline setup

Other skills for the same job

different authors, same section of the catalogue
Webapp Testing
by anthropics
vendor ×12

Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.

6k tokens scripts
Finishing A Development Branch
by ZhanlinCui
×7

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

1k tokens
Test Driven Development
by w95
×7

Use when implementing any feature or bugfix, before writing implementation code

2k tokens
Systematic Debugging
by ratacat
×7

Use when encountering any bug, test failure, or unexpected behavior, before proposing fixes

10k tokens scripts
Verification Before Completion
by ZhanlinCui
×6

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

1k tokens
Backtest Expert
by BaggaT236
×3

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.

15k tokens scripts
Adaptyv
by christophacham
×3

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.

16k tokens
Aeon
by christophacham
×3

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.

19k tokens

How to use it

Copy the folder

Take impertio-studio/frappe-testing-unit from the repository into ~/.claude/skills for personal use, or into .claude/skills inside a project.

Check the name does not clash

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.