Enable Dark Mode!
how-to-write-test-cases-in-odoo-19.jpg
By: Rheshikes PK

How to Write Test Cases in Odoo 19

Technical Odoo 19 Odoo Enterprises Odoo Community

Tests are an important part of Odoo development, offering a way to make sure that any custom modules are stable, safe, and reliable even when the system itself changes. In Odoo 19, testing becomes more advanced by offering such things as performance testing capabilities, advanced APIs, and new automation and GUI testing opportunities.

The blog offers some data about testing Odoo 19 modules, starting from simple unit testing and ending with security testing, scheduled jobs, performance testing, etc.

1. Why Testing Is Essential in Odoo 19

Odoo 19 includes performance enhancements and internal refactoring, which increases the importance of having automated testing tools even further. A good testing framework allows:

  • Find functional bugs during the early stages of development.
  • Avoid regressions on upgrade or refactoring.
  • Ensure correct security and access rights configuration.
  • Describe business processes in an executable form.
  • Keep stable, large multi-module projects.

In short, tests protect both your code and your time.

2. Preparing the Testing Environment in Odoo 19

Odoo 19 still uses the testing facilities provided by Python, along with the testing tools developed within Odoo itself. You cannot start developing any test cases before making sure that your module is well prepared for testing.

Organizing Test Files

your_module/
+-- __init__.py
+-- __manifest__.py
+-- models/
+-- tests/
   +-- __init__.py
   +-- test_models.py
   +-- test_business_logic.py

In this way, Odoo 19 will automatically include your test cases when the module is installed in a test-enabled environment.

3. Understanding the Odoo 19 Test Framework

Odoo 19 utilizes Python's unit test framework along with Odoo's own utility functions found in:

odoo.tests.common

Some of the most popular test cases include:

  • TransactionCase - Tests using ORM that roll back the entire database.
  • SavepointCase - Quick tests that use savepoints rather than rolling back the entire database.
  • HttpCase - For web pages, controllers, and UI processes.

Every test is run in an isolated environment.

4. Writing a Basic Test Case in Odoo 19

Most tests for business logic use the TransactionCase.

from odoo.tests.common import TransactionCase
class TestPartnerCreation(TransactionCase):
   @classmethod
   def setUpClass(cls):
       super().setUpClass()
       cls.partner = cls.env['res.partner'].create({
           'name': 'Sample Partner',
           'is_company': True,
           'email': 'sample@company.com',
       })
   def test_partner_name(self):
       self.assertEqual(self.partner.name, 'Sample Partner')
   def test_partner_company_flag(self):
       self.assertTrue(self.partner.is_company)

In this case, setUpClass is used to prepare the test data that will be used by many test methods.

In this test, we will test the creation of a res.partner object in Odoo.

5. Assertion Methods Often Used in Odoo Test Cases

An assertion is a statement that verifies whether something behaves as expected during a test. Failure of an assertion leads to failure of the test itself.

a. Value Comparison Assertions

These assertions help in the comparison of value and logical outcomes.

assertEqual(a,b) - Confirms that both values are the same.

def test_assert_equal(self):
   partner = self.env['res.partner'].create({
       'name': 'John Doe'
   })
   self.assertEqual(partner.name, 'John Doe')

assertNotEqual(a,b) - Ensures that two values are different.

def test_assert_not_equal(self):
   partner = self.env['res.partner'].create({
       'name': 'John Doe'
   })
   self.assertNotEqual(partner.name, 'Jane Doe')

assertTrue(x) - Passes only if the expression evaluates to True.

def test_assert_true(self):
   partner = self.env['res.partner'].create({
       'name': 'John Doe'
   })
   self.assertTrue(partner.active)

assertFalse(x) - Passes only if the expression evaluates to False.

def test_assert_false(self):
   partner = self.env['res.partner'].create({
       'name': 'John Doe',
       'active': False
   })
   self.assertFalse(partner.active)

b. Object Identity Assertions

These checks are useful when comparing object references rather than values.

assertIs(a,b) - Verifies that both variables refer to the same instance in memory.

def test_assert_is(self):
   partner = self.env['res.partner'].create({
       'name': 'John Doe'
   })
   same_partner = partner
   self.assertIs(partner, same_partner)

assertIsNot(a,b) - Confirms that two variables point to different objects.

def test_assert_is_not(self):
   partner1 = self.env['res.partner'].create({
       'name': 'John'
   })
   partner2 = self.env['res.partner'].create({
       'name': 'Jane'
   })
   self.assertIsNot(partner1, partner2)

assertIsNone(x) - Succeeds if the value is None.

def test_assert_is_none(self):
   value = None
   self.assertIsNone(value)

assertIsNotNone(x) - Succeeds if the value contains something other than None.

def test_assert_is_not_none(self):
   partner = self.env['res.partner'].create({
       'name': 'John Doe'
   })
   self.assertIsNotNone(partner.id)

c. Assertions for Collections

These assertions validate lists, tuples, sets, or recordsets.

assertIn(a,b) - Checks whether an element exists inside a collection.

def test_assert_in(self):
   partner = self.env['res.partner'].create({
       'name': 'John Doe'
   })
   partners = self.env['res.partner'].search([])
   self.assertIn(partner, partners)

assertNotIn(a,b) - Confirms that an element does not exist in a collection.

def test_assert_not_in(self):
   partner = self.env['res.partner'].create({
       'name': 'John Doe'
   })
   companies = self.env['res.company'].search([])
   self.assertNotIn(partner, companies)

assertCountEqual(a,b) - Ensures two collections contain the same items, even if the order differs.

def test_assert_count_equal(self):
   expected = ['A', 'B', 'C']
   actual = ['C', 'A', 'B']
   self.assertCountEqual(expected, actual)

d. Exception Validation Assertions

These are used to confirm that the system correctly blocks invalid operations.

assertRaises(Error,func,*args) - Passes only if the specified error is triggered during execution.

from odoo.exceptions import ValidationError
def test_assert_raises(self):
   with self.assertRaises(ValidationError):
       self.env['your.model'].create({
           # Missing required fields that trigger ValidationError
       })

6. Testing Busiself. Logic Using SavepointCase

SavepointCase module helps us create fast database test cases within Odoo. Unlike other modules that perform a rollback on the entire database after each test case, SavepointCase uses database savepoints and allows us to run tests efficiently without losing isolation among test methods. Below is a sample code of testing business logic using SavepointCase.

from odoo.tests.common import SavepointCase

class TestStudent(SavepointCase):
    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        cls.partner = cls.env['res.partner'].create({
            'name': 'John Doe',
            'email': 'john@example.com',
        })
    def test_partner_name(self):
        self.assertEqual(self.partner.name, "John Doe")
    def test_update_partner(self):
        self.partner.name = "Jane Doe"
        self.assertEqual(self.partner.name, "Jane Doe")

How it works

  1. setUpClass() runs once for the entire test class.
    • A partner record named John Doe is created.
  2. test_partner_name()
    • Checks that the partner's name is John Doe.
  3. test_update_partner()
    • Changes the name to Jane Doe.
    • Verifies the update.

After test_update_partner() is executed, Odoo performs a rollback to the savepoint, thus restoring the database to its previous state.

7. Testing Website Features with HttpCase

For frontend flows such as forms or controllers, HttpCase is the right choice.

from odoo.tests.common import HttpCase
class TestWebsiteFlow(HttpCase):
   def test_contact_form(self):
       self.start_tour(
           '/contactus',
           'website_contact_form_tour',
           login='admin'
       )
       lead = self.env['crm.lead'].search([], limit=1)
       self.assertTrue(lead)

This test helps to make sure that the contact form on a website is functioning properly by testing if the expected backend record gets created through the simulation of an actual user action.

8. Testing Security Rules in Odoo 19

Security test in Odoo 19 makes sure that a user can only do what his/her access rights allow him/her to do. The purpose of this test is to confirm that the restricted actions are blocked successfully.

from odoo.tests.common import TransactionCase
from odoo.exceptions import AccessError

class TestProductSecurity(TransactionCase):
   @classmethod
   def setUpClass(cls):
       super().setUpClass()
       cls.product = cls.env['product.product'].create({
           'name': 'Test Product',
           'list_price': 100.0,
       })
   def test_user_cannot_delete_product(self):
       user = self.env.ref('base.user_demo')
       with self.assertRaises(AccessError):
           self.product.with_user(user).unlink()

This simple test guarantees that users without proper rights cannot perform restricted actions. Writing tests like this helps keep your Odoo modules secure and upgrade-safe.

9. Testing Scheduled Actions in Odoo 19

Scheduled actions (cron jobs) are background tasks that run automatically at configured intervals. This test checks whether such an automated process works correctly when executed.

from odoo.tests.common import TransactionCase

class TestScheduledAction(TransactionCase):
   def test_scheduled_task(self):
       """Verify scheduled job creates records"""
       cron = self.env.ref('your_module.cron_create_partners')
       cron.method_direct_trigger()
       partners = self.env['res.partner'].search([
           ('name', 'like', 'Auto Partner')
       ])
       self.assertTrue(partners)

This test uses TransactionCase to work with the ORM in a controlled test environment. It retrieves a scheduled action by its XML ID and triggers the cron job manually instead of waiting for the scheduler. After execution, it checks whether the expected records were created, and the test fails automatically if the cron does not perform its intended task.

10. Performance Testing in Odoo 19

from odoo.tests.common import TransactionCase
class TestPerformance(TransactionCase):
   def test_bulk_product_creation_performance(self):
       """Ensure bulk product creation uses limited SQL queries"""
       with self.assertQueryCount(less_than=60):
           products = self.env['product.product'].create([
               {
                   'name': f"Product {i}",
                   'list_price': 50.0,
               }
               for i in range(80)
           ])
       self.assertEqual(len(products), 80)

This test creates many product records at once and keeps track of how many SQL queries are executed during the process. By limiting the query count, it ensures the ORM operates efficiently without unnecessary database calls. Finally, it verifies that all intended records were created successfully, confirming both performance and correctness.

11. How to run Tests in Odoo 19

./odoo-bin -c /etc/odoo/odoo.conf -d test_db -i your_module --test-enable --stop-after-init
  • -d test_db specifies the name of the database that will be used to run the tests.
  • -i your_module specifies the module that contains the test cases to be installed and tested.
  • --test-enable enables and runs Python test cases defined in the moduleโ€™s tests directory.
  • --stop-after-init stops the Odoo server automatically after all tests have finished running, making it suitable for CI pipelines.

Test case generation for Odoo 19 is an absolute necessity in order to make sure that the next development stages of your module will be stable and safe enough. You can conveniently check your business logic, SQL, scheduled actions, and other aspects of the application using tools of the Odoo testing framework, such as TransactionCase or HttpCase.

Integration of test cases into your workflow will prevent you from any bugs and issues. So, in conclusion, test cases for Odoo 19 will do your applications a world of good.

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

What is the purpose of writing test cases in Odoo 19?

Writing test cases will make sure that everything in your custom modules works well, avoid regressions, check security rules, etc.

Where should test files be placed in an Odoo module?

Test files should be placed inside a tests/ directory within your module. Odoo automatically detects and runs tests from this folder when testing is enabled.

What is the difference between TransactionCase and SavepointCase?

TransactionCase will roll back all transactions after each test, whereas SavepointCase will use the savepoint method for testing, which is quicker than TransactionCase.

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



0
Comments



Leave a comment



WhatsApp