Tutorials/Testing Methodology

Testing Methodology

πŸ“– This is a condensed reference. For the full guide, see the official Frappe docs: Unit Testing Guide Β· Running Tests


Overview

Frappe's test runner is built on Python's unittest module and integrates directly with Bench. Key rules:

Rule Detail
File naming Must start with test_ and be a .py file
Test site Must run on a site starting with test_ (prevents accidental data loss)
Auto stubs Test stubs are auto-generated when a new DocType is created
Auto fixtures Test runner builds records for all linked (FK) DocTypes automatically
Run command bench run-tests
Frappe Testing Pipeline
Frappe Testing Pipeline β€” from unit tests to parallel CI execution

Writing DocType Tests

Test files live alongside the DocType: test_[doctype].py. Use FrappeTestCase (not bare unittest.TestCase) β€” it resets frappe.local.flags and rolls back the DB transaction after each test.

import frappe
from frappe.tests.utils import FrappeTestCase

def create_events():
    if frappe.flags.test_events_created:
        return
    frappe.set_user("Administrator")
    frappe.get_doc({"doctype": "Event", "subject": "_Test Event 1",
                    "starts_on": "2024-01-01", "event_type": "Public"}).insert()
    frappe.get_doc({"doctype": "Event", "subject": "_Test Event 2",
                    "starts_on": "2024-01-01", "event_type": "Private"}).insert()
    frappe.flags.test_events_created = True

class TestEvent(FrappeTestCase):
    def setUp(self):
        create_events()

    def tearDown(self):
        frappe.set_user("Administrator")

    def test_allowed_public(self):
        frappe.set_user("test1@example.com")
        doc = frappe.get_doc("Event", frappe.db.get_value("Event", {"subject": "_Test Event 1"}))
        self.assertTrue(frappe.has_permission("Event", doc=doc))

    def test_not_allowed_private(self):
        frappe.set_user("test1@example.com")
        doc = frappe.get_doc("Event", frappe.db.get_value("Event", {"subject": "_Test Event 2"}))
        self.assertFalse(frappe.has_permission("Event", doc=doc))

Why FrappeTestCase?

  • Resets frappe.local flags between tests
  • Wraps each test in a DB transaction β†’ rolled back automatically β€” no leftover data
  • Always call super().setUpClass() when overriding setUpClass

Running Tests

# All tests
bench run-tests

# By app
bench run-tests --app erpnext

# By DocType
bench --site test.local run-tests --doctype "Sales Order"

# Specific test method
bench run-tests --doctype User --test test_get_value

# By module (dotted path)
bench run-tests --module erpnext.support.doctype.issue.test_issue

# With Python profiler
bench run-tests --doctype "Activity Cost" --profile

# Skip fixture creation (faster during dev)
bench --site test.local run-tests --doctype "Student Group" --skip-test-records --skip-before-tests

# Verbose output
bench --verbose run-tests

# JUnit XML output (for CI)
bench run-tests --junit-xml-output=/reports/junit_test.xml

Parallel Execution

# Static split β€” divide test files evenly across N instances
bench --site test.local run-parallel-tests --build-id 1 --total-builds 2

# Dynamic split β€” uses Frappe Test Orchestrator for time-balanced distribution
bench --site test.local run-parallel-tests --use-orchestrator

The orchestrator assigns the next test file to whichever CI runner finishes first β€” reducing total pipeline time significantly on large test suites.


Key APIs

API Purpose
frappe.set_user("user@example.com") Switch user context for permission tests
frappe.has_permission("DocType", doc=doc) Check if current user has access
frappe.get_doc({...}).insert() Create a test document
frappe.db.get_value("DocType", {"field": "val"}) Fetch a record name
frappe.get_list("DocType", filters=...) Query list with filters
frappe.in_test True during test runs β€” use to skip side effects
frappe.flags.test_xyz_created Guard flag to avoid duplicate fixture creation

Environment Detection

Use frappe.in_test to skip emails, notifications, or external API calls during test runs:

if not frappe.in_test:
    send_notification_to_user()

XUnit / CI Output

For Jenkins or GitHub Actions, output JUnit-compatible XML:

bench run-tests --junit-xml-output=/reports/junit_test.xml

The XML format is standard XUnit β€” works with any CI system that consumes JUnit reports (Jenkins xUnit plugin, GitHub Actions test reporters, GitLab, etc.).


πŸ“– Full reference: docs.frappe.io β€” Unit Testing

Need help with your workflow setup?

If you're stuck or want help applying these guides to your setup, our team can assist with configuration, customization, and workflow implementation.