Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mock the bound context of a celery task

How do I mock the bound context, or mock the celery task id?

Given a celery task like:

helpers.py:

from task import some_task

def some_helper():
    some_task.delay(123)

in task.py:

@app.task(queue="abc", bind=True)
def some_task(self, some_number: int):
    print(self.id) # how to mock this attribute access?

Simple test case:

from django.test.testcases import TestCase
from helpers import some_helper


class SomeTest(TestCase):

    def test_some_helper(self):
        some_helper()

I tried:

 @patch("celery.app.base.Celery.task", return_value=lambda x: x)

I also tried:

class MockResult(dict):
    def __getattr__(self, x):
        return self[x]

...
def test_some_task(self):
    cls = MockResult({"id": "asdf"})
    bound_some_task = some_task.__get__(cls, MockResult)
    bound_some_task(123)

Related:

  • Python: Bind an Unbound Method?
  • https://docs.python.org/2/howto/descriptor.html
  • Mocking Celery `self.request` attribute for bound tasks when called directly
like image 977
jmunsch Avatar asked Apr 23 '19 22:04

jmunsch


3 Answers

Given a celery task that looks like:

@my_celery_app.task(bind=True)
def my_task(self):
    if self.request.retries == 1:
        my_method_to_invoke()
        # Do work for first retry
    elif self.request.retries == 2:
        # Do work for second retry
    # do work for main task

The test can set the self.request.retries by mocking the base Task.request class attribute within celery.

In the unit test the following can be done

@patch("path.to.task.my_method_to_invoke")
@patch("celery.app.task.Task.request")
def my_test_method(self, mock_task_request, mock_my_method_to_invoke):

    # Set the value of retries directly
    mock_task_request.retries = 1

    # Call the task and assert the inside method was
    # called
    my_task()

    mock_my_method_to_invoke.assert_called_once()

It may be possible to do the same with id on Task. I was lead to this answer looking for how to mock the self on a bound celery task.

like image 138
Garry Polley Avatar answered Nov 09 '22 11:11

Garry Polley


Was able to get something working by using setattr on the task method, not sure if there is a better/other ways to do this:

from django.test.testcases import TestCase
from helpers import some_helper

class SomeTest(TestCase):

    def test_some_helper(self):

        from task import some_task
        setattr(some_task, 'id', 'hello-id')

        some_helper()

In addition to this it is possible to mock the request.id or "task id" like so:

@patch("task.some_task.request.id", return_value="hello-id")
def test_some_helper(...): ....
like image 25
jmunsch Avatar answered Nov 09 '22 10:11

jmunsch


What helped me was to create a mock class mimicking the task

class CeleryTaskHelper:
    """Mock task id"""

    class Request:
        @property
        def id(self):
            return ''.join(random.choices(string.ascii_uppercase + string.digits, k=20))

    @property
    def request(self):
        return self.Request()

    def revoke(self):
        return

and then

@patch('apps.orders.tasks.activate_order.apply_async', return_value=CeleryTaskHelper())
like image 31
Just-Drue Avatar answered Nov 09 '22 11:11

Just-Drue