Odoo Development · intermediate · 11 min read
Testing Odoo Modules
Automated testing for Odoo addons: TransactionCase, SavepointCase, test tags, mocking RPC, view loading checks, and CI patterns for Odoo 16/17.
Last reviewed 2026-07-30 · Estimated effort: 2–6 weeks typical implementation slice
Table of contents
Odoo ships a unittest-based harness. TransactionCase rolls back each test method — safe for create/write on real models. Tests live in tests/ imported from tests/__init__.py and run with --test-enable at install or via tags.
High-value tests cover business invariants: cannot confirm order without credit check, sync idempotent on duplicate webhook, record rules block other company — not trivial assertTrue(True) placeholders.
CI runs odoo-bin on a throwaway database, installs dependency modules plus yours, executes tagged tests, and fails the build on traceback. Pair with staging manual QA for UX — not a substitute.
Works on my machine is not evidence. Odoo projects that skip tests discover regressions at month-end close or upgrade weekend — the most expensive moments.
This article shows where tests live, which case class to pick, and how to assert security and view integrity without brittle full-browser automation for every ticket.
tests/test_sale_custom.py defines classes inheriting TransactionCase. Import test modules in tests/__init__.py. Odoo discovers tests when module installs with --test-enable or when running tagged subsets.
Use @tagged('post_install', '-at_install') to run after full registry load — typical for modules depending on sale, stock, or account being present.
- ▸common.py for shared setup helpers and base test class
- ▸demo data optional — prefer explicit create in setUpClass
- ▸name tests test_<behavior> for readable CI logs
setUpClass creates users, products, partners once per class; each test method gets rolled-back cursor. Use Form from odoo.tests.common for onchange simulation on sale orders and invoices.
assertRaises(UserError) for business rule tests. Compare recordsets with assertEqual(set(rec.ids), {...}) — order may vary.
Switch user: record.with_user(sales_user).read() should raise AccessError when ACL denies. Create records in company A; user in company B should not see them via search when rules enforce.
Cron methods: call directly on model with cron user env, assert side effects and no sudo leaks.
self.env.ref('module.view_id')._get_combined_arch() validates view compiles — catches xpath breaks early. Controller tests use HttpCase with authenticate for route status and JSON payload shape.
Mail: use mail.mail search or mock if volume is high — avoid sending real SMTP in CI.
Pipeline: create DB, install base + dependencies + module under test, run odoo-bin --stop-after-init --test-enable -i module --test-tags /module. Fail on ERROR in log.
Odoo.sh runs tests on push when configured. On-prem GitLab/Jenkins needs Docker image matching prod Odoo version and pinned enterprise hash if used.
- ✓Add at least one test per critical override (action_confirm, validate, invoice post).
- ✓Test idempotent integration methods twice in same test method.
- ✓Run tests before every merge — not only before release.
- ✓Use factory helpers for partners/products to keep tests readable.
- ✓Tag slow tests @tagged('slow') and exclude from default PR pipeline if needed.
- ✓Refresh anonymized prod snapshot tests quarterly for realistic domains.
- !Tests that depend on demo data present only on one developer DB.
- !Shared mutable class state leaking between tests without setUp.
- !Only testing as superuser — missing ACL regressions.
- !HttpCase without proper registry setup — flaky on CI.
- !Disabling tests to green CI instead of fixing race or ordering.
- !No test for migration script — forward upgrade untested.
Tests are the cheapest upgrade insurance and the fastest way to document business rules developers will forget.
Wire them into CI, then treat green builds as prerequisite for staging deploy — covered in deployment practices.
Trigger
Business event starts the flow.
Execute
Operate in the system of record.
Validate
Check outcomes and exceptions.
Close
Reconcile and hand off.
- Owners named
- UAT scripts signed
- Rollback documented
- Hypercare roster ready
Frequently asked questions
TransactionCase vs SavepointCase?
TransactionCase rolls back whole test method. SavepointCase uses savepoints for nested rollback — useful for HttpCase or partial rollback scenarios. Most module tests use TransactionCase.
How do I run one test file locally?
odoo-bin -d test_db -i your_module --test-enable --stop-after-init --test-tags /your_module:TestClass.test_method
Should I test UI with Tour?
Tours (web tours) help critical UX flows — expensive to maintain. Prefer Form and ORM tests for business logic; reserve tours for smoke on main happy path.
Do tests run on module upgrade (-u)?
With --test-enable during -u, tests can run on update path depending on tags. Explicit CI job for -u after merge catches upgrade-only failures.
Related articles
Writing Upgrade-Friendly Code
Engineering patterns that survive Odoo version upgrades: inheritance over fork, xpath stability, migration scripts, noupdate data, and avoiding private API coupling.
View →Deployment Best Practices
Shipping Odoo modules safely: staging parity, -u vs -i, backups, zero-downtime limits, workers, logging, and rollback playbooks for on-prem and Odoo.sh.
View →How the Odoo ORM Works
Environments, recordsets, transactions, prefetch, and the create/write/unlink lifecycle — practical ORM behavior for Odoo 16/17 developers.
View →Security: Groups and Record Rules
Odoo access rights: ir.model.access.csv, security groups, record rules, field-level restrictions, and multi-company patterns that enforce server-side.
View →No tests on business-critical custom modules?
We introduce TransactionCase coverage, CI jobs, and security assertions on your highest-risk flows.
