I'm trying to test a function in one of my models, and am trying to to mock out the filesytem using mock.patch. No matter what I try, it doesn't seem to intercept the method.
Model to test:
app/models.py
from django.db import models
from .utils.storageutils import get_file
from .utils.datautils import derive_data
Class DataThing(models.Model):
#defined here
def set_data_from_file(self):
data = derive_data(get_file('filepath'))
setattr(self, 'derived_data', data)
self.save()
app/utils/datautils.py
import pandas as pd
def derive_data(data_from_file):
df = pd.DataFrame('...')
#do stuff with dataframe
return df
app/tests/unit_tests/tests_models.py
from django.test import TestCase
import mock
from app.models import DataThing
class DataThingModelTest(TestCase):
#classmethod
def setUpTestData(cls):
cls.datathing = DataThing
#mock.patch('app.models.derive_data')
def test_set_data_from_file(self, mocked_derive_data):
mocked_derive_data.return_value=('pretend_dataframe')
self.datathing.set_data_from_file()
self.assertEquals(self.datathing.derived_data, 'pretend_dataframe')
I would expect this to pass. However, I get an error, because datathing.set_data_from_file() is still ultimately calling utils.storageutils.get_file. I've tried patching in app.utils.datautils.derive_data but have the same issue.
I also needed to patch the get_file function
#mock.patch('app.models.get_file')
#mock.patch('app.models.derive_data')
def test_set_data_from_file(self, mocked_derive_data, mocked_ger_file):
mocked_get_file.return_value=('placeholder')
mocked_derive_data.return_value=('pretend_dataframe')
self.datathing.set_data_from_file()
self.assertEquals(self.datathing.derived_data, 'pretend_dataframe')
Related
I am trying to test whether a function defined outside of a class is called by a class method.
def function_a(input):
return input
class MyClass:
def __init__(self, user_function=function_a):
self.user_function = user_function
def function_b(input):
return self.user_function(input)
I am trying to test the function call like this:
from unittest import TestCase
from unittest.mock import Mock
class TestMyClass(TestCase):
def test_function_call(self):
my_class = MyClass(user_function=function_a)
mock = Mock()
my_class.function_b(input)
mock.function_a.assert_called()
This gives me
AssertionError: Expected 'function_a' to have been called.
I am using Python 3.6. Looking at the output I can see that function_a was called. What am I doing wrong?
This here did the trick:
from unittest import TestCase
from unittest.mock import MagicMock
class TestMyClass(TestCase):
def test_function_call(self):
my_class = MyClass(user_function=MagicMock())
my_class.function_b(input)
my_class.user_function.assert_called()
I have following python code inside a function I call with my pytest:
#function.py
def get_object():
object = manager.get_version(entity)
I want to mock this in my test but I don't know what to use as patch url. I know have:
#patch("functions.get_version")
def test_get_object(get_entities: MagicMock,):
#code with fixture
I don't know if this is even possible or the right approach?
You must mock the object manager from function.
Example:
manager.py
def get_version():
return '0.0.1'
functions.py
import manager
def get_object():
object = manager.get_version()
return object
test_function.py
from unittest.mock import MagicMock, patch
from functions import get_object
#patch("functions.manager")
def test_get_object(get_entities: MagicMock):
get_entities.get_version.return_value = '1.0.0'
assert get_object() == '1.0.0'
How can I use a pytest fixture within a TestCase method? Several answers to similar questions seem to imply that my example should work:
import pytest
from django.test import TestCase
from myapp.models import Category
pytestmark = pytest.mark.django_db
#pytest.fixture
def category():
return Category.objects.create()
class MyappTests(TestCase):
def test1(self, category):
assert isinstance(category, Category)
But this always results in an error:
TypeError: test1() missing 1 required positional argument: 'category'
I realize I could just convert this trivial example into a function, and it would work. I would prefer to use django's TestCase because it includes support for importing traditional "django fixture" files, which several of my tests require. Converting my tests to functions would require re-implementing this logic, since there isn't a documented way of importing "django fixtures" with pytest (or pytest-django).
package versions:
Django==3.1.2
pytest==6.1.1
pytest-django==4.1.0
I find it easier to use the "usefixtures" approach. It doesn't show a magical 2nd argument to the function and it explicitly marks the class for having fixtures.
#pytest.mark.usefixtures("category")
class CategoryTest(TestCase):
def test1(self):
assert Category.objects.count() == 1
I opted to rewrite django's fixture logic using a "pytest fixture" that is applied at the session scope. All you need is a single fixture in a conftest.py file at the root of your test directory:
import pytest
from django.core.management import call_command
#pytest.fixture(scope='session')
def django_db_setup(django_db_setup, django_db_blocker):
fixtures = [
'myapp/channel',
'myapp/country',
...
]
with django_db_blocker.unblock():
call_command('loaddata', *fixtures)
This allowed me to throw out the class-based tests altogether, and just use function-based tests.
docs
Why do you need TestCase? I usually use Python class and create tests there.
Example
import pytest
from django.urls import reverse
from rest_framework import status
from store.models import Book
from store.serializers import BooksSerializer
#pytest.fixture
def test_data():
"""Поднимает временные данные."""
Book.objects.create(name='Book1', price=4000)
#pytest.fixture
def api_client():
"""Возвращает APIClient для создания запросов."""
from rest_framework.test import APIClient
return APIClient()
#pytest.mark.django_db
class TestBooks:
#pytest.mark.usefixtures("test_data")
def test_get_books_list(self, api_client):
"""GET запрос к списку книг."""
url = reverse('book-list')
excepted_data = BooksSerializer(Book.objects.all(), many=True).data
response = api_client.get(url)
assert response.status_code == status.HTTP_200_OK
assert response.data == excepted_data
assert response.data[0]['name'] == Book.objects.first().name
I'm trying to write a simple unit test to test an instance method of one of my models in Django. However my class initialises an external connection on __init__ which is not being patched even though I'm trying to target it.
Folder Structure:
- project/
- app1/
- tests/
- tests_models.py
- models.py
models.py:
from 3rdPartyPlugin import Bot
class MyClass:
def __init__(self, token):
self.bot = Bot(token)
def generate_buttons(self):
...
tests_models.py:
from django.test import TestCase
from unittest.mock import MagicMock, patch
from app1.models import MyClass
#patch('app1.models.3rdPartyPlugin.Bot')
class GenerateButtonsTestCase(TestCase):
def setUp(self):
self.tb = MyClass('', '', '')
def test_generates_button_correctly(self):
return True
I can't get past the setUp step because the initialisation of the class fails because it tries to reach out to that 3rdPartyPlugin module even though I patched it.
I've tried setting the patch to:
#patch('app1.models.3rdPartyPlugin.Bot')
#patch('app1.models.Bot')
#patch('app1.models.TB.Bot')
But all of the above still leads to the Bot being called. Any suggestions?
The problem is that Bot is already imported and read in models.py before you patch it.
Try to import the whole module instead:
import 3rdPartyPlugin
class MyClass:
def __init__(self, token):
self.bot = 3rdPartyPlugin.Bot(token)
def generate_buttons(self):
I eventually was able to solve this by moving the 3rdPartyPlugin code out of the initialiser and into an instance method on the class I was testing. This is how it looked in code:
models.py:
from 3rdPartyPlugin import Bot
class MyClass:
def __init__(self, token):
self.token = token
self.bot = self.bot()
def bot(self):
Bot(self.token)
tests_models.py:
from app1.models import MyClass
class GenerateButtonsTestCase(TestCase):
#patch('app1.models.MyClass.bot')
def setUp(self, _):
self.tb = MyClass('', '', '')
And after making the above changes, the patch correctly works and patches my use of Bot from the 3rdPartyPlugin. It's a bit messy but it works for me.
I have a problem roughly looking like this:
In a file data.py I have
from typing import ClassVar
from tinydb import TinyDB
from dataclasses import dataclass
#dataclass
class Data:
db: ClassVar = TinyDB("some_path")
#property
def some_data(self):
return 100
I would like to mock the some_data method.
I tried:
import pytest
import pandas as pd
from package1.data import Data
#pytest.fixture
def mocked_raw_data(mocker):
m = mocker.patch.object(
Data, "some_data", return_value=10, new_callable=mocker.PropertyMock
)
)
return m
def test_some_data(mocked_raw_data):
assert Data().some_data == 2
But obviously this gives an error with the db method class variable. How can I mock this variable as well? Does my approach generally make sense?
Did you use #pytest.mark.django_db?
This would help in testing data on a separate DB rather than the production one.
And regarding your question on mocking, you can use monkey patch for mocking
For eg,
def test_user_details(monkeypatch):
mommy.make('Hallpass', user=user)
return_data =
{
'user_created':'done'
}
monkeypatch.setattr(
'user.create_user', lambda *args, **kwargs: return_data)
user_1 = create_user(user="+123456789")
assert user_1.return_data == return_data