Celery task behave strangely while testing - python

I'm trying to test that my Celery task updates Django model. It works fine, but behaves strangely during testing.
# someapp/models.py
class SomeModel(models.Model):
...
hidden = models.BooleanField(default=False)
# someapp/tasks.py
#shared_task()
def my_task(model_id):
model_instance = someapp.models.SomeModel.objects.get(id=model_id)
model_instance.hidden = True
model_instance.save()
logger.info(f'Uncovered model instance with id {model_id]')
To test this I've implemented following workflow:
I create SomeModel object via factory-boy factory because SomeModel depends on multiple models.
I assign this object to variable model_instance
I apply the task locally
I assert that model_instance.hidden is True
The code below
# someapp/tests.py
#pytest.mark.django_db
#pytest.mark.celery(task_always_eager=True)
def test_celery_task_uncovers_model_instance() -> None:
SomeModelFactory.create(hidden=False)
some_model = someapp.models.SomeModel.objects.first()
assert some_model.hidden is True
my_task.apply((some_model.id, ))
assert some_model.hidden is True
raises at the last line. Then I assert:
assert (model_instance.pk, model_instance.hidden) == (someapp.models.SomeModel.objects.first().pk,
someapp.models.SomeModel.objects.first().hidden)
It raises:
E assert (1, True) == (1, False)
E At index 1 diff: True != False
Finally, I want to check ids:
assert id(model_instance) == id(authapp.models.SomeModel.objects.first())
And it raises something like this:
E AssertionError: assert 139938217188656 == 139938219885184
E + where 139938217188656 = id(<SomeModel: - 2022-02-01>)
E + and 139938219885184 = id(<SomeModel: - 2022-02-01>)
Why does not the task update the some_model object in my test?

The instance was updated in the task but not in your test, you need to refresh the instance's data from the DB using Model.refresh_from_db() after calling the task
#pytest.mark.django_db
#pytest.mark.celery(task_always_eager=True)
def test_celery_task_uncovers_model_instance() -> None:
SomeModelFactory.create(hidden=False)
some_model = someapp.models.SomeModel.objects.first()
assert some_model.hidden is False
my_task.apply((some_model.id, ))
some_model.refresh_from_db() # <<<
assert some_model.hidden is True

Related

Mock object with pyTest

I have a function I need to test:
def get_user_by_username(db, username: str) -> Waiter:
"""Get user object based on given username."""
user = db.query(Waiter).filter(Waiter.username == username).first()
return user
Here I try to mock DB call and return correct Waiter object, so my test is:
def test_get_user_by_username(mocker):
waiter = Waiter(id=10, username="string", password="1111")
db_mock = mocker.Mock(return_value=waiter)
result = get_user_by_username(db=db_mock, username="string")
assert result.json()["username"] == "string"
But I get an error TypeError: 'Mock' object is not subscriptable. How I can do it?
According to #Don Kirkby answer, the right solution is:
def test_get_user_by_username(mocker):
waiter = Waiter(id=10, username="string", password="1111")
filter_method = mocker.Mock()
filter_method.first = mocker.Mock(return_value=waiter)
query = mocker.Mock()
query.filter = mocker.Mock(return_value=filter_method)
db_mock = mocker.Mock()
db_mock.query = mocker.Mock(return_value=query)
db_mock.query.filter.first = mocker.Mock(return_value=waiter)
result = get_user_by_username(db=db_mock, username="string")
assert vars(result)["username"] == "string"
You're calling several methods, so you need to mock all of them:
query()
filter()
first()
Here's my best guess at how to do that with your code:
def test_get_user_by_username():
waiter = Waiter(id=10, username="string", password="1111")
filter_method = mocker.Mock()
filter_method.first = mocker.Mock(return_value=waiter)
query = mocker.Mock()
query.filter = mocker.Mock(return_value=filter_method)
db_mock = mocker.Mock()
db_mock.query = mocker.Mock(return_value=query)
db_mock.query.filter.first = mocker.Mock(return_value=waiter)
result = get_user_by_username(db=db_mock, username="string")
assert result.json()["username"] == "string"
I'm not sure what database library you're using, but I recommend looking for a library that provides mock versions of the database methods. For example, I contributed a few features to django-mock-queries that lets you mock out the Django ORM. Either that, or separate the logic you want to test from the database access code.

pytest-mock assert_called_with failed for class function

I am planning to use pytest and pytest-mock for validating the Python code. Being a newbie, wrote a sample code to validate the mock on class and seeing failure. I am wondering what went wrong.
src/main.py
class Main(object):
def __init__(self, my_var=None):
self.var = my_var
def internal_func(self, var=10):
my_var = var + 20
return my_var
def test_func(self):
val = self.internal_func(20)
return val + 40
tests/test_main.py
import pytest
from pytest_mock import mocker
from src.main import Main
def new_func(cls, *args, **kwargs):
return 2
def test_main_mock(mocker):
mocker.patch.object(Main, 'internal_func')
val = Main().test_func()
assert Main.internal_func.assert_called_with(20)
It fails with the following error
======================================================================================== FAILURES ========================================================================================
_____________________________________________________________________________________ test_main_mock _____________________________________________________________________________________
mocker = <pytest_mock.MockFixture object at 0x7f34f490d8d0>
def test_main_mock(mocker):
mocker.patch.object(Main, 'internal_func')
main = Main()
val = main.test_func()
# assert val == 80
> assert Main.internal_func.assert_called_with(20)
E AssertionError: assert None
E + where None = <bound method MagicMock.wrap_assert_called_with of <MagicMock name='internal_func' id='139865418160784'>>(20)
E + where <bound method MagicMock.wrap_assert_called_with of <MagicMock name='internal_func' id='139865418160784'>> = <MagicMock name='internal_func' id='139865418160784'>.assert_called_with
E + where <MagicMock name='internal_func' id='139865418160784'> = Main.internal_func
tests/test_main.py:13: AssertionError
The return_value or side_effect must be set before the patched func take effect
def test_main_mock(mocker):
# mock internal_func of class Main
mocked_internal_func = mocker.patch.object(Main, 'internal_func')
# assign return_value or side_effect
mocked_internal_func.return_value = -10
# class instance
ma = Main()
val = ma.test_func()
assert ma.internal_func.assert_called_with(20)
Correction of mistake, the assert should not be used together with assert_called_with, they are independent assert.
assert val == 30
mocked_internal_func.assert_called
ma.internal_func.assert_called_with(20)
mocked_internal_func.assert_called_with(20)

Split a test in different functions with pytest

I'm using pytest and have multiple tests to run to check an issue.
I would like to split all tests into different functions like this:
# test_myTestSuite.py
#pytest.mark.issue(123)
class MyTestSuite():
def test_part_1():
result = do_something()
assert result == True
def test_part_2():
result = do_an_other_something()
assert result == 'ok'
of course, I implemented issue in conftest.py
# conftest.py
def pytest_addoption(parser):
group = parser.getgroup('Issues')
group.addoption('--issue', action='store',
dest='issue', default=0,
help='')
but I don't know how to hook once after testing MyTestSuite and check that all tests of MyTestSuite correctly passed.
Does anyone have any ideas?
PS: this is my first post on StackOverflow.
Try to use the return function as most simple type of positive debug conformation as shown below.
#pytest.mark.issue(123)
class MyTestSuite():
def test_part_1():
result = do_something()
assert result == True
return 'tp1', True
def test_part_2():
result = do_an_other_something()
assert result == 'ok'
return 'tp2', True
..and then where you run your tests from:
x = MyTestSuite().test_part_1()
if x[1] == True:
print 'Test %s completed correctly' % x[0]
The result after running test1:
Test tp1 completed correctly, or...
AssertionError.
Collecting assertion errors:
collected_errors = []
def test_part_1():
testname = 'tp1'
try:
result = do_something()
assert result == True
return testname, True
except Exception as error:
info = (testname, error)
collected_errors.append(info)
More assertion flavours you can find here on SO.

How to avoid a trip to the database in this Test case

How do I override the django model with a Factory object so as to avoid hitting the database.
models.py
from django.db import models
class ApplicationType(models.Model):
"""
Types of applications available in the system/
"""
title = models.CharField(max_length=30)
def __str__(self):
return self.title
utils.py
from .models import ApplicationType
self.base_details = {}
def get_application_type(self, value):
"""
Get types of applications. When successful it Populates the
self.base_details with an application_type key
Args:
value (object): value to be parsed
Returns:
bool: True when value is ok, Else false
Raises:
"""
item_name = "Application Type"
self.base_details['application_type'] = None
try:
if value:
try:
result = ApplicationType.objects.get(title=value) # <== How do I avoid hitting this DB object?
self.base_details['application_type'] = result.id
return True
except ApplicationType.DoesNotExist:
self.error_msg = "Invalid Value: {}".format(item_name)
return False
else:
self.error_msg = "Blank Value: {}".format(item_name)
return False
except:
raise
So to test, I create an ApplicationType factory
tests.py
import factory
import pytest
application_types = ['Type 1', 'Type 2']
class ApplicationTypeFactory(factory.Factory):
class Meta:
model = ApplicationType
title = "application_type_title"
#pytest.mark.django_db()
def test_get_application_type_populates_dict_when_value_provided_exists_in_database(self):
"""Populates base_details dict when value is found in database"""
for entry in application_types:
application_type = ApplicationTypeFactory.build(title=entry)
assert self.base_info_values.get_application_type(entry) == True
assert self.base_info_values.base_details["application_type"] is not None
As such, how would you go about writing a test that will avoid hitting the database in the ApplicationType.objects.get() query which is smack in the middle of code? Can I pass the "Model" as an parameter to the function and would that be a good design?
You are free to provide an alternative structure for the application/functions especially to allow for better testing in this kind of scenario.
Am running Python3.5, pytest-django and factory_boy
You can patch the call to the database, to return a predefined value set by you. In your case, you could do something like this:
import factory
import pytest
from unittest.mock import Mock, patch
application_types = ['Type 1', 'Type 2']
#pytest.mark.django_db()
#patch('ApplicationType.objects.get')
def test_get_application_type_populates_dict_when_value_provided_exists_in_database(self, db_mocked_call):
"""Populates base_details dict when value is found in database"""
mocked_db_object = {'id': 'test_id'}
db_mocked_call.return_value = mocked_db_object
for entry in application_types:
application_type = ApplicationTypeFactory.build(title=entry)
assert self.base_info_values.get_application_type(entry) == True
assert self.base_info_values.base_details["application_type"] is not None
I suggest you to check as well pytest.parametrize to avoid making use of the for loop in your test, read more about it here: http://doc.pytest.org/en/latest/parametrize.html
In your example the test could look something like the following:
#pytest.mark.django_db()
#pytest.mark.parametrize("entry", ['Type 1', 'Type 2'])
#patch('ApplicationType.objects.get')
def test_get_application_type_populates_dict_when_value_provided_exists_in_database(self, db_mocked_call, entry):
"""Populates base_details dict when value is found in database"""
mocked_db_object = {'id': 'test_id'}
db_mocked_call.return_value = mocked_db_object
application_type = ApplicationTypeFactory.build(title=entry)
assert self.base_info_values.get_application_type(entry) == True
assert self.base_info_values.base_details["application_type"] is not None

How to assign a value to a property in django

i have a new property in my model however I'd like to assign a test value in it for my test script.
this is my code:
models.py
mycode = models.UUIDField(null=True)
#property
def haveCode(self):
if self.mycode == uuid.UUID('{00000000-0000-0000-0000-000000000000}'):
return False
else
return True
and this is the test script that i am working on. I wanted to have a test value for haveCode:
test = Test()
test.mycode = uuid.UUID('{00000000-0000-0000-0000-000000000000}')
test.save()
checkTest = Test()
#this is only to pass the test
#delete this when start coding
checkTest.haveCode = True
assertEqual(test.haveCode, True)
however I got an error in checkTest.haveCode = True since this is just a property and not an attribute.
how to assign True to it? I appreciate your help
You can 'mock' that property using the mock library
from mock import patch, PropertyMock
#patch.object(Test, 'haveCode', new_callable=PropertyMock)
def myTest(test_haveCode_mock):
test_haveCode_mock.return_value = True
checkTest = Test()
assertEqual(checkTest.haveCode, True)
patch.stopall() # when you want to release all mocks

Categories