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
Related
I want to include a custom class into a route's response. I'm mostly using nested pydantic.BaseModels in my application, so it would be nice to return the whole thing without writing a translation from the internal data representation to what the route returns.
As long as everything inherits from pydantic.BaseModel this is trivial, but I'm using a class Foo in my backend which can't do that, and I can't subclass it for this purpose either. Can I somehow duck type that class's definition in a way that fastapi accepts it? What I have right now is essentially this:
main.py
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Foo:
"""Foo holds data and can't inherit from `pydantic.BaseModel`."""
def __init__(self, x: int):
self.x = x
class Response(BaseModel):
foo: Foo
# plus some more stuff that doesn't matter right now because it works
#app.get("/", response_model=Response)
def root():
return Response(foo=Foo(1))
if __name__ == '__main__':
import uvicorn
uvicorn.run("main:app") # RuntimeError
It's not documented, but you can make non-pydantic classes work with fastapi. What you need to do is:
Tell pydantic that using arbitrary classes is fine. It
will try to jsonify them using vars(), so only straight forward
data containers will work - no using property, __slots__ or stuff like that[1].
Create a proxy BaseModel, and tell Foo to offer it if someone
asks for its schema - which is what fastapis OpenAPI pages do.
I'll just assume that you want them to work too since they're
amazing.
main.py
from fastapi import FastAPI
from pydantic import BaseModel, BaseConfig, create_model
app = FastAPI()
BaseConfig.arbitrary_types_allowed = True # change #1
class Foo:
"""Foo holds data and can't inherit from `pydantic.BaseModel`."""
def __init__(self, x: int):
self.x = x
__pydantic_model__ = create_model("Foo", x=(int, ...)) # change #2
class Response(BaseModel):
foo: Foo
#app.get("/", response_model=Response)
def root():
return Response(foo=Foo(1))
if __name__ == '__main__':
import uvicorn
uvicorn.run("main:app") # works
[1] If you want more complex jsonification, you need to provide it to the Response class explicitly via Config.json_encoders.
Here is a full implementation using a subclass with validators and extra schema:
from psycopg2.extras import DateTimeTZRange as DateTimeTZRangeBase
from sqlalchemy.dialects.postgresql import TSTZRANGE
from sqlmodel import (
Column,
Field,
Identity,
SQLModel,
)
from pydantic.json import ENCODERS_BY_TYPE
ENCODERS_BY_TYPE |= {DateTimeTZRangeBase: str}
class DateTimeTZRange(DateTimeTZRangeBase):
#classmethod
def __get_validators__(cls):
yield cls.validate
#classmethod
def validate(cls, v):
if isinstance(v, str):
lower = v.split(", ")[0][1:].strip().strip()
upper = v.split(", ")[1][:-1].strip().strip()
bounds = v[:1] + v[-1:]
return DateTimeTZRange(lower, upper, bounds)
elif isinstance(v, DateTimeTZRangeBase):
return v
raise TypeError("Type must be string or DateTimeTZRange")
#classmethod
def __modify_schema__(cls, field_schema):
field_schema.update(type="string", example="[2022,01,01, 2022,02,02)")
class EventBase(SQLModel):
__tablename__ = "event"
timestamp_range: DateTimeTZRange = Field(
sa_column=Column(
TSTZRANGE(),
nullable=False,
),
)
class Event(EventBase, table=True):
id: int | None = Field(
default=None,
sa_column_args=(Identity(always=True),),
primary_key=True,
nullable=False,
)
as per #Arne 's solution you need to add your own validators and schema if the Type you are using has __slots__ and basically no way to get out a dict.
Link to Github issue: https://github.com/tiangolo/sqlmodel/issues/235#issuecomment-1162063590
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 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')
I am using mock module for Python 2.7 to mock my other functions and using
unittest for writing unit tests.
I am wondering if mocking the MongoDB is different than using mock functionality (mock.patch a function that is being called?) Or I need to use another different package for that purpose?
I do not think I want to have a test mongodb instance running. All I want is some tempo data and being able to call pymongo functionality. I am just a bit lost in thinking of is there a way to write a mock for a module (like pymongo), or anything is achievable by mock module.
So appreciate if you could provide an example or tutorial on this.
Code to Test
from pymongo import MongoClient
monog_url = 'mongodb://localhost:27017'
client = MongoClient(monog_url)
db = client.db
class Dao(object):
def __init__(self):
pass
def save(self, user):
db_doc = {
'name': user.name,
'email': user.email
}
db.users.save(db_doc)
def getbyname(self, user):
db_doc = {
'name': user.name,
}
return db.users.find(db_doc)
To test this, I do not really want a test mongodb up and running! But also, I think I do not want to mock db.userssave and db.users.find because I want to actually be able to retrieve the data that I saved and make sure it is in the db. I think I need to create some fixtures per models that are in my memory and work with them. Just do I need an external tool to do so?
I am thinking of keeping some fake data like this, just do not know how to properly deal with it.
users = {
{'name' : 'Kelly', 'email' : 'kelly#gmail.com'},
{'name': 'Sam', 'email': 'sam#gmail.com'}
}
I recommend using mongomock for mocking mongodb. It's basically an in-memory mongodb with pymongo interface and made specifically for this purpose.
https://github.com/mongomock/mongomock
You can also do this if you're just doing something simple, and you don't really need to retrieve by field.
#mock.patch("pymongo.collection.Collection.find")
def test_name(self, mock_find):
mock_find.return_value = {'name' : 'Kelly', 'email' : 'kelly#gmail.com'}
# rest of test
You can certainly mock PyMongo, but I recommend mocking the MongoDB server itself. I've written a pure-Python emulator for MongoDB that can control fully, which responds to MongoDB Wire Protocol messages however you choose:
http://mockupdb.readthedocs.io/tutorial.html
Here's an example of using MockupDB with a Python application:
https://emptysqua.re/blog/test-mongodb-failures-mockupdb/
It requires intimate knowledge of the MongoDB wire protocol, but that's a useful skill to acquire anyway.
Adding to #mirthbottle answer, if you want to access attribute of mongo object as a field you can do it as,
class MongoDummybject:
def __init__(self, _data):
for _d in _data:
setattr(self, _d, _data[_d])
return_data = {'name' : 'Nishant', 'email' : 'nishant#gmail.com'}
#mock.patch("pymongo.collection.Collection.find")
def test_name(self, mock_find):
mock_find.return_value = MongoDummybject(return_data)
For unit testing exception wrapped within custom exception, patch functions(e.g.bulk_write) within Collection of mongomock
#mock.patch("mongomock.collection.Collection.bulk_write", side_effect=BulkWriteError({}))
def test_bulk_wrt_err(self, blk_wrt_err):
with self.assertRaises(SpecialBulkWriteExcep) as context:
add_user()
Sample code here
Mongomock is recommended way for testing, here's a runnable example to get started:
client.py
from dataclasses import dataclass
import pymongo
monog_url = 'mongodb://localhost:27018'
client = pymongo.MongoClient(monog_url)
db = client.db
#dataclass
class User:
name: str
email: str
class Dao:
def save(self, db, user):
db_doc = {
'name': user.name,
'email': user.email
}
return db.users.insert_one(db_doc).inserted_id
def get_by_name(self, db, user):
db_doc = {
'name': user.name,
}
return db.users.find(db_doc)
test_client.py
import mongomock
import pytest
from client import Dao, User
class TestDao:
#pytest.fixture
def user(self):
yield User(name='John', email='test#gmail.com')
#pytest.fixture
def dao(self):
yield Dao()
#pytest.fixture
def client_mock(self):
yield mongomock.MongoClient()
#pytest.fixture
def mock_db(self, client_mock):
yield client_mock.db
def test_save(self, mock_db, dao, user):
id = dao.save(mock_db, user)
users = list(mock_db.users.find())
assert [obj for obj in users if obj['_id'] == id]
assert len(users) == 1
def test_get_by_name(self, mock_db, dao, user):
dao.save(mock_db, user)
found_user = next(dao.get_by_name(mock_db, user))
found_user.pop('_id')
assert found_user == user.__dict__
unknown_user = User(name='Smith', email='john#gmail.com')
found_user = next(dao.get_by_name(mock_db, unknown_user), None)
assert found_user is None
Using #funnydman's example.
client.py
from pymongo import MongoClient
class MongoDB(object):
def __init__(self) -> None:
self.MONGO_URI ='mongodb://localhost:27018'
self.client = MongoClient(self.MONGO_URI)
self.default_db = self.client.db
def ingest_one(self, document: any, collection_name: str, db_name: str = None):
if document:
db_name = self.client[db_name] if db_name else self.default_db
return db_name[collection_name].insert_one(document).inserted_id
def find(self, query: dict, collection_name: str, db_name: str = None):
db_name = self.client[db_name] if db_name else self.default_db
return db_name[collection_name].find(query)
test_client.py
import mongomock
import pytest
import pytz
import datetime
from dataclasses import dataclass
from db.mongo_db import MongoDB
localtz = pytz.timezone('your_time_zone')
#dataclass
class Client:
document: dict
class TestMongoDB:
#pytest.fixture
def client(self):
yield Client(document={
"name": "Juan Roman",
"requestDate": str(datetime.datetime.now(localtz))
})
#pytest.fixture
def mongo_db(self):
yield MongoDB()
#pytest.fixture
def client_mock(self):
yield mongomock.MongoClient()
#pytest.fixture
def mock_db(self, client_mock):
yield client_mock.db
def test_ingest_one(self, mock_db, mongo_db, client):
id_client = mongo_db.ingest_one(client.document, mock_db.collection.name, mock_db.name)
stored_obj = mongo_db.find({'_id': id_client}, mock_db.collection.name, mock_db.name)
assert [obj for obj in stored_obj if str(obj['_id']) == str(id_client)]