5

Does anyone know how can you write mock tests for Odoo objects?

I have these classes and methods:

my_module:

from odoo import models


class MyModel(models.Model):

    _name = 'my.model'

    def action_copy(self):
        IrTranslation = self.env['ir.translation']
        for rec in self:
            if rec.translate:
                IrTranslation.force_translation(rec)

my_module_2:

from odoo import models


class IrTranslation(models.Model):
    _inherit = 'ir.translation'

    def force_translation(self, rec):
        # do stuff

When I call it, I want to test if IrTranslation.force_translation was called in action_copy method and how many times.

But this method is not imported directly, it is referenced through env.

If let say force_translation would be imported like:

from my_module_2.IrTranslation import force_translation

def action_copy(self):
    # do stuff.
    force_translation()

Then I could try doing something like this:

from unittest import mock
from my_module import action_copy

 def test_some_1(self):
        with mock.patch('my_module.my_module_2.IrTranslation') as mocked_translation:
            action_copy()
            mocked_translation.force_translation.assert_called_once()

But because modules in Odoo are not imported directly (like you do it in plain Python), I don't understand how to specify methods in Odoo environment to be mocked.

P.S. I also did not see any mocked tests in standard Odoo, except for base classes that do not inherit Model class -> which then you need to use its _inherit attribute instead of importing class and passing it to be inherited on another class.

Andrius
  • 19,658
  • 37
  • 143
  • 243
  • 1
    That's a really interesting question, Odoo's machinery sure makes mocking `imports` almost irrelevant. Maybe you could mock the registry itself? But then you would have to make sure every call to `env` returns something usable (and there will be a lot of them). – yorodm Sep 04 '18 at 16:26
  • Have you taken a look at `odoo.tests` package and specifically at `odoo.tests.common` at one of the `assert*` functions there? `assertQueryCount` seems that can be of use to you. Also, before that make sure that you go through https://www.odoo.com/documentation/11.0/reference/testing.html. I am not sure if what you are asking is possible. – George Daramouskas Sep 04 '18 at 19:25
  • @GeorgeDaramouskas where do you see that function? I'm lookin at https://github.com/odoo/odoo/blob/11.0/odoo/tests/common.py but can't seem to find `assertQueryCount` – Andrius Sep 04 '18 at 19:33
  • @Andrius I missed the 11.0 tag, so no that is not there that method exists on master. And that probably might not help you. In any case, if it is not in `odoo.tests` or in python's unittest then you have to resort to something custom. – George Daramouskas Sep 04 '18 at 19:38
  • Very interesting question indeed. – CZoellner Sep 05 '18 at 09:30

2 Answers2

3

Testing in Odoo does not use the concept of mocking. Instead, tests are derived from standard base classes. The standard class TransactionalTest opens a transaction and never commits it, but rolls it back to undo any changes.

This is obviously not the same as regular mocking in that you can't replace other methods or classes to return fixed/expected values and/or avoid other side effects apart from persisting changes in the database, like sending emails or calling a remote web service.

miw
  • 776
  • 4
  • 11
  • IIRC you usually write initial methods to initiate records of business objects to test with. So you could monkey patch class methods on that time and after testing "re"-monkey patch to original methods. That could be a possibility of mocking for that part. – CZoellner Sep 05 '18 at 09:28
2

It can be done. I do it all the time since Odoo 8.0 (until 15.0 now). The key is to know where to patch. Odoo adds odoo.addons to your module's package when its imported so in your case, you may do the following:

from odoo import tests
from mock import patch
from odoo.addons.my_module_2.models.ir_translations import IrTranslation


class TestMyModule2(tests.TransactionCase):
    def some_test_1(self):
        my_model = self.env['my.model'].create({})

        with patch.object(IrTranslation, 'force_translation') as mocked_translation:
            my_model.action_copy()

        mocked_translation.assert_called_once()

Or using just patch, then no need to import:

with patch('odoo.addons.my_module_2.models.ir_translations.IrTranslation.force_translation') as mocked_translation:
    my_model.action_copy()

This patches your specific method in your specific class. This way you can also target the method of a super class.

If you need to patch a method and you don't care where it is or where it's overriden, just patch using Python's type() (then no need to import class):

with patch.object(type(self.env['ir.translation']), 'force_translation') as mocked_translation:
    my_model.action_copy()

Some additional notes to save you some headaches:

  • If you use pyCharm, don't mock socket objects. It messes with pyCharm's mechanismes. Better to put your calls to socket into a one line method and mock that method instead.
  • datetime.datetime.now() cannot be mocked, as all builtin types, but fields.Datetime.now() can.
Jerther
  • 5,558
  • 8
  • 40
  • 59