Odoo has a built-in testing framework based on Python's unittest. This skill helps you write TransactionCase unit tests, HttpCase integration tests, and JavaScript tour tests. It also covers running tests in CI pipelines.
--test-enable.@odoo-automated-tests and describe the feature to test.odoo CLI command to execute your tests.# tests/test_hospital_patient.py
from odoo.tests.common import TransactionCase
from odoo.tests import tagged
from odoo.exceptions import ValidationError
@tagged('post_install', '-at_install')
class TestHospitalPatient(TransactionCase):
@classmethod
def setUpClass(cls):
# Use setUpClass for performance — runs once per class, not per test
super().setUpClass()
cls.Patient = cls.env['hospital.patient']
cls.doctor = cls.env['res.users'].browse(cls.env.uid)
def test_create_patient(self):
patient = self.Patient.create({
'name': 'John Doe',
'doctor_id': self.doctor.id,
})
self.assertEqual(patient.state, 'draft')
self.assertEqual(patient.name, 'John Doe')
def test_confirm_patient(self):
patient = self.Patient.create({'name': 'Jane Smith'})
patient.action_confirm()
self.assertEqual(patient.state, 'confirmed')
def test_empty_name_raises_error(self):
with self.assertRaises(ValidationError):
self.Patient.create({'name': ''})
def test_access_denied_for_other_user(self):
# Test security rules by running as a different user
other_user = self.env.ref('base.user_demo')
with self.assertRaises(Exception):
self.Patient.with_user(other_user).create({'name': 'Test'})
setUpClassvssetUp: UsesetUpClass(Odoo 15+) for shared test data. It runs once per class and is significantly faster thansetUpwhich re-initializes for every single test method.
# Run all tests for a specific module
./odoo-bin --test-enable --stop-after-init -d my_database -u hospital_management
# Run only tests tagged with a specific tag
./odoo-bin --test-enable --stop-after-init -d my_database \
--test-tags hospital_management
# Run a specific test class
./odoo-bin --test-enable --stop-after-init -d my_database \
--test-tags /hospital_management:TestHospitalPatient
from odoo.tests.common import HttpCase
from odoo.tests import tagged
@tagged('post_install', '-at_install')
class TestPatientController(HttpCase):
def test_patient_page_authenticated(self):
# Authenticate as a user, not with hardcoded password
self.authenticate(self.env.user.login, self.env.user.login)
resp = self.url_open('/hospital/patients')
self.assertEqual(resp.status_code, 200)
def test_patient_page_redirects_unauthenticated(self):
# No authenticate() call = public/anonymous user
resp = self.url_open('/hospital/patients', allow_redirects=False)
self.assertIn(resp.status_code, [301, 302, 403])
setUpClass() with cls.env instead of setUp() — it is dramatically faster for large test suites.@tagged('post_install', '-at_install') to run tests after all modules are installed.ValidationError, AccessError, UserError).self.with_user(user) to test access control without calling sudo().TransactionCase test is rolled back in isolation.HttpCase.authenticate() — use self.env.user.login or a fixture user.phantomjs or Chrome headless) and a live Odoo server — not covered in depth here.HttpCase tests are significantly slower than TransactionCase — use them only for controller/route verification.cr.commit()) can leak state between tests.