Python patch context manager to return object - python

I am trying to patch a context manager that does a database lookup and returns an object like follows:
class MyClass:
#contextlib.contextmanager
def client_ctx(self, id):
# hidrate from DB and yield object
yield client # instance of SQAlchemy model Client
def run(self, id):
with self.client_ctx(id) as cl:
# do work here
Client class in this case is a SQLAlchemy model.
In my tests I am trying to patch this method client_ctx to simply return an object instantiated in the tests like this:
#patch('MyClass.client_ctx')
def test_ctx(self, _mocked_ctx_manager):
myclass = MyClass()
client = Client(
id=1,
code='test-client')
_mocked_ctx_manager.__enter__.return_value = client
myclass.run(1)
I'm getting: TypeError: Object of type MagicMock is not JSON serializable which makes no sense to me. What am I doing wrong, is there a better way to mock a context manager ?

The following should work:
_mocked_ctx_manager.return_value.__enter__.return_value = client
Your _mocked_ctx_manager returns a context manager. Therefore you need to set the __enter__.return_value of _mocked_ctx_manager.return_value.
I found the following article to be helpful: Surrender Python Mocking! I Have You Now.

Related

How to create a singleton object in Flask micro framework

I am creating a class for Producer which pushes messages to RabbitMQ. It makes use of pika module.
I would like to create a handler so that I have control over the number of connections that interact with Rabbit MQ.
Is there a way we can add this to app_context and later refer to that or is there way that we use init_app to define this handler.
Any code snippet would be of really good help.
In Python, using singleton pattern is not needed in most cases, because Python module is essentially singleton. But you can use it anyway.
class Singleton(object):
_instance = None
def __init__(self):
raise Error('call instance()')
#classmethod
def instance(cls):
if cls._instance is None:
cls._instance = cls.__new__(cls)
# more init operation here
return cls._instance
To use Flask (or any other web framework) app as singleton, simply try like this.
class AppContext(object):
_app = None
def __init__(self):
raise Error('call instance()')
#classmethod
def app(cls):
if cls._app is None:
cls._app = Flask(__name__)
# more init opration here
return cls._app
app = AppContext.app() # can be called as many times as you want
Or inherite Flask class and make itself as a singleton.

How can I mock an object instantiated in the constructor?

I'm writing unit-tests with Pytest. I want to unit-test a class that has on its __init__ method an object that connects to a database:
data_model.py
from my_pkg.data_base_wrapper import DataBaseWrapper
class DataModel:
def __init__(self):
self.db = DataBaseWrapper()
self.db.update_data()
def foo(self):
data = self.db.get_some_data()
# make some processing and return a result
data_base_wrapper.py
class DataBaseWrapper:
def __init__(self):
# Init process of the wrapper
pass
def update_data(self):
# Connect to the database and perform some operations
pass
I've tried using monkeypatch on the DataBaseWrapper object of DataModel:
from my_pkg.data_model import DataModel
class MockDataBaseWrapper:
#staticmethod
def update_cache():
pass
#staticmethod
def get_table(table):
# Return some data for testing
pass
#pytest.fixture
def data_model(monkeypatch):
monkeypatch.setattr(DataModel, 'db', MockDataBaseWrapper)
data_model = DataModel()
return data_model
However, I get the following error:
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f10221669e8>
#pytest.fixture
def data_model(monkeypatch):
> monkeypatch.setattr(DataModel, 'db', MockDataBaseWrapper)
E AttributeError: <class 'dashboard.datamodel.DataModel'> has no attribute 'db'
I've read in the answers of similar questions, that I could try writing a sub-class of my DataBaseWrapper and change it on the DataModel class, but I'm in the same situation, as I cannot monkeypatch the attribute of the __init__ method. I can, though, if it is not in the __init__ method.
How can I write tests for this composition of classes? Suggestions on how to rewrite these classes or a different patter are also welcome.
The problem is that your MockDataBaseWrapper is totally unrelated to the DataBaseWrapper used in DataModel.
My proposal is to get rid of your MockDataBaseWrapper and:
If you want to keep your current structure, you can use patch to mock the DataBaseWrapper that is actually imported in data_model.py.
from mock import patch
from my_pkg.data_model import DataModel
def test_data_model():
with patch("my_pkg.data_model.DataBaseWrapper") as MockedDB:
mocked_db = MockedDB()
data_model = DataModel()
assert data_model.db is mocked_db
The patch context manager will replace the DataBaseWrapper class that gets imported in your data_model.py with a Mock instance and let you interact with that mock, which allows me here to verify that it got instantiated.
Note that it is very important to patch the class in the module where it is imported (and not in the model where it is defined, i.e we patch your_package.data_model.DataBaseWrapper and not your_package.data_base_wrapper.DataBaseWrapper)
If you don't mind changing your class, then the usual pattern is to inject the db parameter into the constructor of DataModel. Mocking it then becomes a piece of cake.
class DataModel:
def __init__(self, db):
self.db = db
self.db.update_data()
from mock import patch, Mock
from my_pkg.data_model import DataModel
def test_data_model():
mocked_db = Mock()
data_model = DataModel(mocked_db)
assert data_model.db is mocked_db

inject library dependencies into django model

I have a Django model that makes use of some libraries which I would like to be able to override. For instance, when testing I'd like to pass a mock instead of having my model tightly coupled. I can do this in python, but for the life of me I can't figure out how to do it with a Django model. Here's a simplified example not using Django:
import requests
class APIClient:
def __init__(self, **kwargs):
self.http_lib = kwargs.get("http_lib", requests)
def get_url(self, url):
return self.http_lib.get(url)
For regular use of this class I can still use requests but if I want to use a different library for some reason or if I want to test certain outcomes, I can invoke the class with client = APIClient(http_lib=MockRequests())
But how do I do that with a Django model? If I try to pass kwargs that aren't backed by a database field Django throws an error. Overriding __init__ is not considered a good practice either. Is there a way in Django to set and get a value that isn't backed by a database column?
Do you have a settings.TEST var? If so, you could make http_lib a function that returns the proper lib:
from django.conf import settings
def get_http_lib(mock=None):
if not mock:
return requests
return MockRequests()
class APIClient(Model):
def __init__(self, **kwargs):
# ...whatever...
#property
def some_column(self):
http_lib = get_http_lib(settings.TEST)
# ...etc...
Not ideal, but passable.
PRE-EDIT ANSWER (doesn't work):
What if you setattr subsequent to instantiating the Model?
# In model...
class APIClient(Model):
def __init__(self, **kwargs):
self.http_lib = requests
# ...etc...
# In tests...
client = APIClient()
setattr(client, 'http_lib', MockRequests())

Django : How can I mock request.session of view?

I'm using mocking to test views.
tests.py
#patch('orders.views.OrderView.generate_merchant_uid')
def test_expected_price_is_registered_on_GET_request(self, mock_generate_merchant_uid):
self.client.get(reverse('orders:order'))
views.py
class OrderView(LoginRequiredMixin, View):
def generate_merchant_uid(self):
merchant_uid = "blah_blah_blah"
return merchant_uid
def get(self, request, *args, **kwargs):
merchant_uid = self.generate_merchant_uid()
request.session['merchant_uid'] = merchant_uid
return HttpResponse('a')
It occurs errors:
TypeError: <MagicMock name='generate_merchant_uid()' id='4431843456'> is not JSON serializable
It occurs error because I mocked generate_merchant_uid and it returns MagicMock and View trying to store this MagicMock in the request.session.
I think what I have to do is to mock request.session.
But have no idea how I can do that.
Need advices. Thanks.
The problem is not about mocking the session itself. You forgot to set what your mocked function should return. By default it returns a Mock object and it is trying to store it request session and converting it to JSON, there is where you got the error, Mock instance is not JSON serializable.
#patch('orders.views.OrderView.generate_merchant_uid')
def test_expected_price_is_registered_on_GET_request(self, mock_generate_merchant_uid):
mock_generate_merchant_uid.return_value = //here goes your mocked value
self.client.get(reverse('orders:order'))
For example:
mock_generate_merchant_uid.return_value = "blah_blah_blah"

How to mock an external api in django?

I'm trying to mock the "self.api.friends.get" method in VKAuth class:
import vk
class VKAuth(object):
def __init__(self, access_token, user):
self.session = vk.Session(access_token = access_token)
self.api = vk.API(self.session)
def follow(self):
vk_friends = self.api.friends.get()
from the test module test_views.py:
from mock import patch
from ..auth_backends.vk_backend import VKAuth
class AddUsersToList(TestCase):
#patch.object(VKAuth.api.friends, 'get')
def test_auth_vk(self, mock_get):
... etc ...
And I get an error during testing:
AttributeError: <class 'accounts.auth_backends.vk_backend.VKAuth' doens't have the attribute 'api'
What am I doing wrong? How to get an access to this method in this class structure?
You're trying to mock a class itself, not it's instance. And the class doesn't have the api attribute, as it's created in your __init__(). Change your code to:
def test_auth_vk(self, mock_get):
vk_auth = VKAuth(access_token, user)
with mock.patch('vk_auth.api.friends') as friends_mock:
friends_mock.get.return_value = None
# Invoke the code that calls your api, passing the "vk_auth" variable as a backend.
# ...
friends_mock.mock.get.assert_called_with(your_arguments)
If you can't just pass an auth backend to your code, look up the place where it is instantiated and mock that place.

Categories