Mocking a class in a Flask API - python-3.x

I have three files
helper.py
class helper:
def __init__(self, out_file):
self.out_file = out_file
def foo(first, second):
# Write data to file
flask_API.py
from helper import helper
#app.route('/', methods=['POST'])
def parse_request():
content = request.get_json()
out_file = #based on timestamp
helper(out_file).foo(content['first'], content['second'])
test_flask.py
import unittest
from unittest.mock import patch
import flask_API
class testFlaskAPI(unittest.TestCase):
def setUp(self):
self.app = flask_API.app.test_client()
self.app.test = True
#patch('flask_API.app.helper', return_value=None)
def test_service(self, mock_helper):
response = self.app.post(base_url, data=json.dumps({"some":"value"}, content_type='application/json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
I am having trouble mocking the helper class. This gives me an error saying
AttributeError: <Flask 'flask_API'> does not have the attribute 'helper'
I read that a class/method needs to be mocked where it is being called instead of where it's defined. Is there something wrong with the way I am patching the class?

In the end the solution turned out to be fairly simple. First there was no need to add app in the #patch decorator. The test just needed #patch('flask_API.helper'). Second, I first needed to return the mock of the class and then mock the function call as well. So the final answer turned out to be
#patch('flask_API.helper')
def test_service(self, mock_helper):
mocking_helper = mock_helper.return_value # mocking the class
mocking_helper.foo.return_value = None
response = self.app.post(base_url, data=json.dumps({"some":"value"}, content_type='application/json')
self.assertEqual(response.status_code, status.HTTP_200_OK)

Related

Not able to create mock object using mocker

I am trying to create mock object for unit testing but somehow always actual object is called.
Below is the code for reference:-
utility_functions.py
import os
import json
def get_module_configurations(key):
config = os.getcwd()
config = config + "\\xyz.json"
with open(config) as f:
module_config = json.load(f)
module_config = module_config[key]
return module_config
load_class
from importlib import import_module
from inspect import isclass, isabstract
def load_module(data):
package_path = data['Package']
module_name = data['ModuleName']
class_name = data['ClassName']
try:
module_name = str(module_name.split('.py')[0])
module = import_module('.' + module_name, package_path)
except Exception as error:
pass
try:
_class = getattr(module, class_name)
except Exception as error:
pass
if isclass(_class) and not (isabstract(_class)):
return _class
else:
return None
function1.py
import load_class
from utility_functions import get_module_configurations
def load_helpers(task_name):
module = get_module_configurations(task_name)
cls = load_class.load_module(module)
return cls
test_function.py
import pytest
from function1 import load_helpers
def test_mock(mocker):
class_to_load = {"Package": "Test", "ModuleName": "default_class.py", "ClassName":
"DefaultClass"}
mocker.patch('function1.load_helpers', return_value= class_to_load)
result = load_helpers('c')
assert result is not None
Since I am mocking, load helpers should not be called but it always calls actual implementation saying path is invalid.
I am missing something basic but cannot figure out what.
Any help will be great.
If you are importing the function into your module (from function1 import load_helpers), you need to patch it as if it was part of it. This means that instead of...
mocker.patch('function1.load_helpers', return_value=class_to_load)
...you should use...
mocker.patch('test_function.load_helpers', return_value=class_to_load)
PS: I assume that you are just practicing mocking because otherwise your test function doesn't make sense.

Pytest object created by object assert_called_once_with

I known how I can test if an injected object was called with a specific argument. But in my case the injected object will create an object that object will create another object and I want to test if that last object was called with the right argument.
in the example below the question would be if c.dirve was called with 100 as argument:
class car:
def drive(self, distance):
print("so fast")
class car_shop:
def buy_car(self):
return car()
class shop_shop:
def buy_shop(self):
return car_shop()
class processor:
def __init__(self, sshop):
self.sshop = sshop
def run(self):
cshop = self.sshop.buy_shop()
c = cshop.buy_car()
c.drive(100)
def main():
sshop = shop_shop()
proc = processor(sshop)
proc.run()
if __name__ == "__main__":
main()
is there a way to test that?
Since this was requested here my approach for testing these objects:
import pytest
from unittest.mock import Mock
from object_returns_object_test_for_arguments import processor, shop_shop
#pytest.fixture
def mock_shop_shop():
return Mock(spec=shop_shop)
def test_processor_car_called_with_100(mock_shop_shop):
proc = processor(mock_shop_shop)
proc.run()
assert mock_shop_shop.car_shop.car.drive.assert_called_once_with(100)
assert mock_shop_shop.car_shop.car.drive.call_count == 1
If using just the code shown in the question, you only have to mock car.drive. This could be done for example this way:
from unittest import mock
from object_returns_object_test_for_arguments import processor, shop_shop
#mock.patch('object_returns_object_test_for_arguments.car.drive')
def test_processor_car_called_with_100(drive_mock):
proc = processor(shop_shop())
proc.run()
drive_mock.assert_called_once_with(100)
As I don't know your real code, you may have to mock more stuff.
As an aside: class names in Python are written upper-case, camelcase-style by default.

How to use a pytest fixture to instantiate a object under test?

It appears that fixtures should be used to instantiate an object under test for pytest, especially if it is used by several test_ functions. However, after trying to adapt examples given in the pytest doc, I cannot get the following to work.
import pytest
...
#pytest.fixture
def newMyClass():
obj = MyClass(1,2)
...
def test_aMethod(newMyClass):
objectUnderTest = newMyClass.obj
...
There are no complaints about the fixture or the constructor, but then I receive the pytest error
def test_aMethod(newMyClass):
> objectUnderTest = newMyClass.obj()
E AttributeError: 'NoneType' object has no attribute 'obj'
If fixtures can be used for this, how should that be coded?
To clean up #hoefling's answer, you need to instantiate your class directly and return that instance. Check out this code if you're looking for a cleaned up version.
import pytest
class MyClass():
def __init__(self, obj, foo):
self.obj = obj
self.foo = foo
#pytest.fixture
def newMyClass():
myClassInstance = MyClass(1,2)
return myClassInstance
def test_aMethod(newMyClass):
objectUnderTest = newMyClass.obj
assert objectUnderTest

Python - Mock class init that instantiates another class inside

I have the following python file board.py:
def __init__(self, language):
self.foo = Foo(language)
self.words = Aux(self.foo)
And I'm creating this test_file:
#classmethod
def setUpClass(cls):
super().setUpClass()
cls.board = Board('pt')
def test_total_time(self):
self.board.total_time(True)
#some assert
But I'm getting a FileNotFoundError because Aux.___init____() calls a self.foo.method() that opens a file and reads from it.
Is there a way to mock self.foo.method(), or the class Aux?
You will want to patch the module. If you give me the name of the test file and the class you are testing. I can finish this answer for you.
In the test file:
import unittest
def BoardTestCase(unittest.TestCase):
#classmethod
def setUpClass(cls):
super().setUpClass()
cls.aux_mock = unittest.mock.patch('file_undertest.Aux')
cls.board = Board('pt')
def test_total_time(self):
self.board.total_time(True)
#some assert
I would suggest using pytest instead of the standard library unittest. Your tests will be written as functions meaning you can reuse the Board class only when needed to. You can set more robust fixtures (Board class test cases) and the mocker extension is more intuitive if you spend the 15 minutes to wrap your head around it.

python3 mock doesn't work for all paths

The Production file (production_file.py) is:
class MyError(Exception):
pass
class MyClass:
def __init__(self):
self.value = None
def set_value(self, value):
self.value = value
def foo(self):
raise RuntimeError("error!")
class Caller:
def bar(self, smth):
obj = MyClass()
obj.set_value(smth)
try:
obj.foo()
except MyError:
pass
obj.set_value("str2")
obj.foo()
Test file (test.py):
import unittest
from unittest.mock import patch
from unittest.mock import call
from production_file import MyClass, Caller
class MyTest(unittest.TestCase):
def test_caller(self):
with patch('production_file.MyClass', autospec=MyClass) as MyClassMock:
my_class_mock_obj = MyClassMock.return_value
my_class_mock_obj.foo.side_effect = [MyError("msg"), "text"]
caller = Caller()
caller.bar("str1")
calls = [call("str1"), call("str2")]
my_class_mock_obj.set_value.assert_has_calls(calls)
if __name__ == '__main__':
unittest.main()
This above works. But if I move the production classes (MyError, MyClass, Caller) into the test file, and update patch to:
with patch('test.MyClass', autospec=MyClass) as MyClassMock:
then the instance method "foo" is no longer mocked.
Does anybody have any idea why that is?
I have also experienced a similar problem with some more complex code, where the production code is in my_package/src/production_file.py while the test is in my_package/tests/test_file.py. Python yields no error for the path, the path is correct, but still the mock doesn't work.
If you are running test.py as __main__ then it is not test.MyClass it would be __main__.MyClass, or in both cases __name__+".MyClass".
I was able to determine that the class used and the class patched were different by adding a print statement:
class Caller:
def bar(self, smth):
print(MyClass) #lets see what we are actually making an instance of...
obj = MyClass()
...
When the patch is applied to the class that this is using you would see something like this:
<MagicMock name='MyClass' spec='MyClass' id='4387629656'>
But when the class in moved into test.py you will see something like:
<class '__main__.MyClass'>
Which indicates:
There was no patching applied to MyClass (at least the one that is used for the test.)
The name of the class that needs to be patched is __main__.MyClass
However It is quite likely that your "more... complicated situation" is not working because of a setup like this:
from production_file import MyClass
class MyError(Exception):
pass
class Caller:
def bar(self, smth):
print(MyClass)
obj = MyClass()
...
class MyTest(unittest.TestCase):
def test_caller(self):
with patch('production_file.MyClass', autospec=MyClass) as MyClassMock:
...
In this case production_file.MyClass is being patched and MyClass is being imported from production_file so the correct class is being patched but still the output is:
<class 'production_file.MyClass'>
This is because the Class was directly imported to the local namespace, so when the patch is applied to the production_file the local namespace is still unaffected, we can check that the patch was actually applied with:
...
def bar(self, smth):
print(MyClass)
from production_file import MyClass as pf_MyClass
print(pf_MyClass)
...
#output:
<class 'production_file.MyClass'>
<MagicMock name='MyClass' spec='MyClass' id='4387847136'>
If this is the case you just need to import the module, not the class directly. Then once the patch is applied you will be using it right from the file:
import production_file
...
class Caller:
def bar(self, smth):
print(production_file.MyClass)
obj = production_file.MyClass()
...
class MyTest(unittest.TestCase):
def test_caller(self):
with patch('production_file.MyClass', autospec=MyClass) as MyClassMock:
...

Resources