Enable Dark Mode!
overview-of-unit-tests-in-odoo-19.jpg
By: Muhammed Fahis V P

Overview of Unit Tests in Odoo 19

Technical Odoo 19 Odoo Enterprises Odoo Community

While developing custom modules in Odoo, most of the attention usually goes into getting features to work correctly. But as the project grows, things change, new requirements come in, logic gets refactored, and Odoo itself gets upgraded. This is where unexpected issues start appearing.

Unit testing helps prevent this. Instead of manually checking every feature after a change, tests validate the core logic automatically. Odoo 19 provides a built-in testing framework that allows developers to test business logic in a controlled environment using real ORM behavior.

This article explains how unit tests work in Odoo 19, how to write them properly, and how to use tools like tagged and users to make tests more realistic.

What Are Unit Tests in Odoo?

A unit test verifies a small piece of logic in isolation. In Odoo, this typically includes:

  • Model methods
  • Computed fields
  • Constraints
  • Access rules
  • Business actions

Odoo’s test framework loads a temporary database, executes the logic, and rolls everything back after each test. This means tests do not affect real data and can be run repeatedly.

Test Types Supported by Odoo

Odoo supports multiple test levels:

  • Unit tests – Focus on individual methods or rules
  • Integration tests – Validate interactions between models
  • UI (tour) tests – Simulate frontend behavior

This blog focuses on unit tests, which form the base of a reliable codebase.

Test Directory Structure

All tests must be placed inside the tests folder of a module.

custom_module/
+-- models/
+-- views/
+-- __manifest__.py
+-- tests/
    +-- __init__.py
    +-- test.py

Make sure the test files are imported in tests/__init__.py.

The most commonly used base class is:

from odoo.tests.common import TransactionCase

TransactionCase runs each test inside a database transaction and rolls it back after execution. This keeps the database clean and avoids data conflicts.

Other available base classes include SavepointCase, but TransactionCase is preferred for most unit tests.

Writing a Basic Unit Test

Let’s assume we extend the Sale Order model with a custom discount rule.

Business Logic Example

class SaleOrder(models.Model):
    _inherit = 'sale.order'
    def get_discount_rate(self):
        for order in self:
            if order.amount_total > 10000:
                return 10
            return 0

Creating the Test Case

Test File: test_sale_order.py

from odoo.tests.common import TransactionCase
class TestSaleOrderDiscount(TransactionCase):
    def setUp(self):
        super().setUp()
        self.partner = self.env['res.partner'].create({
            'name': 'Test Customer'
        })
        self.product = self.env['product.product'].create({
            'name': 'Test Product',
            'list_price': 5000,
        })

The setUp method prepares test data and runs before every test.

Testing Business Logic

def test_discount_applied(self):
        order = self.env['sale.order'].create({
            'partner_id': self.partner.id,
            'order_line': [(0, 0, {
                'product_id': self.product.id,
                'product_uom_qty': 3,
                'price_unit': 5000,
            })]
        })
        discount = order.get_discount_rate()
        self.assertEqual(discount, 10)

This verifies that the discount logic behaves as expected.

Testing Edge Conditions

def test_no_discount_for_small_amount(self):
        order = self.env['sale.order'].create({
            'partner_id': self.partner.id,
            'order_line': [(0, 0, {
                'product_id': self.product.id,
                'product_uom_qty': 1,
                'price_unit': 3000,
            })]
        })
        discount = order.get_discount_rate()
        self.assertEqual(discount, 0)

Edge cases are just as important as standard flows.

Using tagged in Unit Tests

What Is tagged?

tagged is a decorator that controls when a test runs. Some tests should not run during module installation, especially if they depend on fully loaded data.

from odoo.tests.common import tagged

Example: Post-Installation Test

from odoo.tests.common import TransactionCase, tagged
@tagged('post_install', '-at_install')
class TestInvoicePosting(TransactionCase):
    def test_invoice_posting(self):
        invoice = self.env['account.move'].create({
            'move_type': 'out_invoice',
            'partner_id': self.env.ref('base.res_partner_1').id,
            'invoice_line_ids': [(0, 0, {
                'name': 'Service',
                'quantity': 1,
                'price_unit': 1500,
            })],
        })
        invoice.action_post()
        self.assertEqual(invoice.state, 'posted')

Explanation:

  • post_install > run after installation
  • at_install > skip during installation

This avoids failures caused by incomplete setup.

Custom Tags

@tagged('sale', 'discount')
class TestSaleDiscount(TransactionCase):

Custom tags help organize large test suites and filter them when needed.

Using users in Unit Tests

What Is users?

By default, tests run as Administrator, which bypasses access rules. The users decorator runs tests under a specific user’s permissions.

from odoo.tests.common import users

Example: Testing Access Rights

from odoo.tests.common import TransactionCase, users
from odoo.exceptions import AccessError
class TestSaleOrderSecurity(TransactionCase):
    @users('salesman')
    def test_salesman_can_create_order(self):
        order = self.env['sale.order'].create({
            'partner_id': self.env.ref('base.res_partner_1').id
        })
        self.assertTrue(order)

This test runs with salesman-level access, not admin access.

Example: Blocking Unauthorized Access

@users('user')
    def test_normal_user_cannot_confirm_order(self):
        order = self.env['sale.order'].create({
            'partner_id': self.env.ref('base.res_partner_1').id
        })
        with self.assertRaises(AccessError):
            order.action_confirm()

This ensures record rules and access rights are working as intended.

Testing Constraints

from odoo.exceptions import ValidationError
def test_negative_quantity_not_allowed(self):
    with self.assertRaises(ValidationError):
        self.env['sale.order.line'].create({
            'product_id': self.product.id,
            'product_uom_qty': -2,
            'price_unit': 1000,
        })

This confirms that invalid data is correctly rejected.

Running Unit Tests in Odoo 19

To run all tests:

./odoo-bin -d test_db --test-enable --stop-after-init

To run tests for a specific module:

./odoo-bin -d test_db -i custom_module --test-enable --stop-after-init

Test results appear clearly in the logs.

Best Practices for Unit Testing

  • Keep each test focused on one behavior
  • Avoid relying on demo data
  • Use users to validate security
  • Use tagged to control execution timing
  • Name tests clearly so failures are easy to understand

Unit testing in Odoo 19 is not an optional extra; it’s a practical way to protect business logic from breaking over time. With Odoo’s built-in testing tools, developers can validate behavior, enforce security rules, and catch issues early in the development cycle.

Using tools like tagged and users, tests can closely reflect real usage scenarios instead of ideal admin-only cases. This results in modules that are easier to maintain, safer to upgrade, and more reliable in production environments.

To read more about How to Write a Test Case in Odoo 18 ERP, refer to our blog How to Write a Test Case in Odoo 18 ERP.


Frequently Asked Questions

Do unit tests modify real business data?

No. Odoo runs unit tests inside a temporary database transaction and rolls everything back after each test, so real data is never affected.

When should I use TransactionCase vs SavepointCase?

Use TransactionCase for most unit tests. SavepointCase is useful for faster tests when database commits are not required.

Can unit tests access the real ORM and models?

Yes. Odoo unit tests use the full ORM, meaning all validations, constraints, and business logic behave exactly like production.

Why should I avoid relying on demo data in tests?

Demo data may not always be installed or consistent across environments. Creating test data explicitly makes tests stable and predictable.

If you need any assistance in odoo, we are online, please chat with us.



0
Comments



Leave a comment



Recent Posts

whatsapp_icon
location

Calicut

Cybrosys Technologies Pvt. Ltd.
Neospace, Kinfra Techno Park
Kakkancherry, Calicut
Kerala, India - 673635

location

Kochi

Cybrosys Technologies Pvt. Ltd.
1st Floor, Thapasya Building,
Infopark, Kakkanad,
Kochi, India - 682030.

location

Bangalore

Cybrosys Techno Solutions
The Estate, 8th Floor,
Dickenson Road,
Bangalore, India - 560042

Send Us A Message