How to mock an external api in django? - python

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.

Related

How to mock a method inside a #singleton decorated class in python

The class itself calls in the init method a get_credentials method, which does I need to mock. Using unittest for mocking;
from unittest import TestCase, mock
from src.layer.utils.db import Db
#singleton
class Db:
def __init__(self):
self.get_credentials()
def get_credentials(self):
# stuff
pass
#Tried and failed:
#mock.patch('src.layer.utils.db.Db.get_credentials',get_creds_mock)
#mock.patch.object(Db, 'get_credentials', get_credentials_mock)
class DbMock:
def get_credentials(self):
pass
def get_credentials_mock(self):
pass
class TestDb(TestCase):
#mock.patch.object(Db, 'get_credentials', get_credentials_mock)
def test_init(self):
db = Db()
self.assertIsInstance(db, Db)
The code of the #singleton decorator class:
def singleton(cls):
instances = {}
def instance():
if cls not in instances:
instances[cls] = cls()
return instances[cls]
return instance
I need to mock the get_credentials function because it communicates with a server, which is not allowed in testing environment. So, I must return the json token itself.
Is there any feasible approach to mock that function?
You could use a solution like https://pypi.org/project/singleton-decorator/ which tackles exactly your problem.
if you cannot exchange the singleton decorator, because it is some kind of framework solution, with this particular solution you are stranded, because you cannot access the instances dictionary.
If you cannot for any reason use another package, but can modify your definition of the singleton wrapper, you could add this to your singleton code:
def singleton(cls):
instances = {}
def instance():
if cls not in instances:
instances[cls] = cls()
return instances[cls]
instance.__wrapped__ = cls
return instance
and then you should be able to override as presented in the package:
#mock.patch('wherever.Db.__wrapped__.get_credentials')

Python patch context manager to return object

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.

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

Django Test mock instance of module variable

I'm trying to test my Django app which has a proxy API which is instantiated in its own module.
api.py
class ProxyApi(object):
def __init__(self, server_info):
pass
def validate_login(self, credentials):
# call to real api here
api = ProxyAPi()
middlewares.py
from mymodule.api import api
class MyMiddleware(MiddlewareMixin):
def process_request(self, request):
if api.validate_login():
# do something with proxy api
views.py
from mymodule.api import api
class TaskView(LoginRequiredMixin, FormView):
def get(self, request):
if api.validate_login():
# do something with proxy api
tests.py
class InputTasksViewTest(TestCase):
#mock.patch('mymodule.api.ProxyAPi')
def test_add(self, mock_api):
mock_api.validate_login.return_value = True
response = self.client.get(reverse('task'))
The original validate_loginis still called.
I would like to know how to handle the instantiation of ProxyApi while still retaining mocking capacity.
Ok I found my own solution, the problem was that once Django started, it read some files (like models or views or middlewares) that automatically instantiated api variable from import.
I just needed to defer this instantiation so I can mock the ProxyApi object, here's what I did:
api = SimpleLazyObject(lambda: ProxApi())
You have def validate_login(self, credentials): in api
and in middleware you define below code. So How you will send creadentials to API from middleware api.validate_login(<You should send credentials to api as parameter>):
from mymodule.api import api
class MyMiddleware(MiddlewareMixin):
def process_request(self, request):
if api.validate_login():
pass

Mocking a class method that is used via an instance

I'm trying to patch a class method using mock as described in the documentation. The Mock object itself works fine, but its methods don't: For example, their attributes like call_count aren't updated, even though the method_calls attribute of the class Mock object is. More importantly, their return_value attribute is ignored:
class Lib:
"""In my actual program, a module that I import"""
def method(self):
return "real"
class User:
"""The class I want to test"""
def run(self):
l = Lib()
return l.method()
with patch("__main__.Lib") as mock:
#mock.return_value = "bla" # This works
mock.method.return_value = "mock"
u = User()
print(u.run())
>>>
mock
<MagicMock name='Lib().method()' id='39868624'>
What am I doing wrong here?
EDIT: Passing a class Mock via the constructor doesn't work either, so this is not really related to the patch function.
I have found my error: In order to configure the methods of my mock's instances, I have to use mock().method instead of mock.method.
class Lib:
"""In my actual program, a module that I import"""
def method(self):
return "real"
class User:
"""The class I want to test"""
def run(self):
l = Lib()
return l.method()
with patch("__main__.Lib") as mock:
#mock.return_value = "bla" # This works
mock().method.return_value = "mock"
u = User()
print(u.run())
from mock import *
class Lib:
"""In my actual program, a module that I import"""
def method(self):
return "real"
class User:
"""The class I want to test"""
def run(self, m):
return m.method()
with patch("__main__.Lib") as mock:
#mock.return_value = "bla" # This works
mock.method.return_value = "mock"
print User().run(mock)
I mock classmethods like this:
def raiser(*args, **kwargs):
raise forms.ValidationError('foo')
with mock.patch.object(mylib.Commands, 'my_class_method', classmethod(raiser)):
response=self.admin_client.get(url, data=dict(term='+1000'))

Categories