Tutorial: ERPNext Python Testing

Testing ERPNext or apps on Frappe Framework will be a-bit confusing because of each doctype will have dependencies and it a-bit hard to understand for newcomers (and for me).

So here is my attempt to explain or understand it.

First of all you will need to create new site just for testing and allow test on that site.

# create new testing site
bench new-site testing.apps --mariadb-root-password 12345 --admin-password admin
# allow test on testing site
bench --site testing.apps set-config allow_tests true

# install custom app if needed
bench --site testing.apps install-app your_app

Then you will be good to go to write testing file, follow testing document from frappe.

https://frappeframework.com/docs/user/en/testing#writing-tests

A-bit more clarification on test file.

  • setUp and tearDown is method from unittest.TestCase, it will be run once per test
    • setUp will be run before running test method
    • tearDown will be run after running test, whether test method succeeded or not
  • test_dependencies (list of doctype name) vairable will be use to define dependencies doctype
  • test_record will be test record in dict format, normally frappe will use frappe.get_test_records then store test record in test_records.json file
  • test_dependencies or test_record won’t work outside doctype test file.

Running test

More command can be seen at testing document as link above.

bench --site testing.apps --verbose run-tests --app frappe
bench --site testing.apps --verbose run-tests --app your_app
bench --site testing.apps --verbose run-tests --module frappe.tests.test_api

Miscellaneous

  • If some thing not working as expected, you might want to reset testing site first before start to debugging.
bench --site testing.apps --force reinstall --mariadb-root-password 12345 --yes --admin-password admin
  • You could run testing site, to see how test record are created.
bench --site testing.apps start
  • When writing test, if the record are created you should delete that record so you could run the test multiple time. Adding frappe.db.rollback() to tearDown will rollback database after each test.
def tearDown(self):
    frappe.db.rollback()`
  • It’ll be a good idea to add before_test function and use that to complete setup wizard.
def before_tests():
    # complete setup if missing
    if not int(frappe.db.get_single_value('System Settings', 'setup_complete') or 0):
        setup_complete({
            'language': 'English',
            'email': 'testing@test.co',
            'full_name': 'Taster',
            'password': 'tastyTestTaste',
            'country': 'Thailand',
            'timezone': 'Asia/Bangkok',
            'currency': 'THB',
        })

    frappe.db.commit()
    frappe.clear_cache()
  • For testing outside of doctype test, and testing need to get record from doctype you could do it on setUp function
from frappe.test_runner import make_test_records

class TestBlogQuery(unittest.TestCase):
    def setUp(self):
        make_test_records(
            doctype='Blog Post',
            verbose=frappe.flags.print_messages,
            force=True,
        )

    def tearDown(self):
        pass

    def test_get_blog_list(self):

Overview Testing flow

Here is how testing works.

  1. Click command run-test > frappe.commands.utils.run_tests
  2. Run frappe.test_runner.main
  3. Run before_test function from hook.py
  4. Run test [All test / For module / For doctype]
    1. Test will create dependent record based on link fields and test_dependencies variable in each test file > frappe.test_runner.get_dependencies
    2. From test_dependencies it’ll create test records > frappe.test_runner.make_test_records_for_doctype
      1. From test_records variable in test file, which frappe normally use frappe.get_test_records function to get test records or it’ll create test record from it’s own doctype
      2. frappe.get_test_records will get test records which store in test_records.json file in each doctype folder
      3. Create test object from test_records using frappe.test_runner.make_test_objects, it’ll skip if record is already exist
18 Likes