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.

WhatsApp