unittest mock cannot find attribute - python

I'm getting the following error:
AttributeError: <class 'workflow.workflow.Task'> does not have the attribute 'extract'
This is how the codes are arranged
src
|_ workflow
|_ workflow.py
|_ tests
|_ test_extract.py
|_ data_extractor:
|_ data_extractor.py
This is workflow.py:
from data_extractor.data_extractor import DataExtractor
class Task:
def __init__(self) -> None:
self.extractor = DataExtractor()
def extract_data(self):
obj = self.extractor.extract()
In test_extract.py:
from unittest import mock, TestCase
from workflow.workflow import Task
class TestSomeExtract(TestCase):
#mock.patch("workflow.workflow.Task.extract")
def test_extract_from_snowflake(self, mock_extract):
actual_result = Task.extract_data()
self.assertTrue(actual_result)
if __name__ == "__main__":
TestCase.main()
I think I did it right but...
UPDATE 24/6:
In test_extract.py:
import unittest
from unittest import mock
from workflow.workflow import DataExtractor
#mock.patch("workflow.workflow.DataExtractor")
class TestSomeExtract(unittest.TestCase):
def test_extract_from_snowflake(self, mock_extract):
mock_extract.return_value.extract.return_value = True
actual_result = DataExtractor().extract(name="entities", key="11") # return a list
self.assertTrue(actual_result)
mock_extract.assert_called_once_with(actual_result)
if __name__ == "__main__":
unittest.main()
In workflow.py:
from data_extractor.data_extractor import DataExtractor
class Task:
def __init__(self, type: str, name: str) -> None:
self.name = name
self.type = type
self.extractor = DataExtractor()
def extract_data(self):
obj = self.extractor.extract(name=self.name, key=key)
Not much of difference besides I added assert_called_once_with in the test case.

Here is a working code example, based in your updated version:
import unittest
from unittest import mock
#mock.patch("workflow.workflow.DataExtractor")
class TestSnowFlakeExtract(unittest.TestCase):
def test_extract_from_snowflake(self, mock_extract):
from workflow.workflow import DataExtractor
mock_extract.return_value.extract.return_value = True
actual_result = DataExtractor().extract(name="stuff", key="11")
self.assertTrue(actual_result)
Some things to highlight:
When we mock something, we replace the attribute. So #mock.patch("workflow.workflow.DataExtractor") replaces the DataExtractor attribute inside workflow.workflow package with a mock object - it doesn't affect the data_extractor.data_extractor package, so we shouldn't use the data_extractor.data_extractor package directly.
Emphasising the previous point: #mock.patch("workflow.workflow.DataExtractor") is translated into these two statements:
import workflow.workflow as module_to_be_patched
module_to_be_patched.DataExtractor = MagicMock()
The mock/patch happens at the beginning of our test, so if the following import statement had been global: from workflow.workflow import DataExtractor, we wouldn't use the mocked version, since we first name DataExtractor inside our test module to be the original data_extractor.data_extractor object and only then execute the patch statement, which only affects the workflow module.
You need to be exact on the mock return syntax: you are calling DataExtractor() which is equivalent to mock_extract.return_value and then you chain the .extract(name="stuff", key="11") which is equivalent to .extract.return_value. Hence the full syntax should be mock_extract.return_value.extract.return_value = True
In actual testing we wouldn't be importing DataExtractor from workflow.workflow, but rather import workflow.workflow and call its functionality, relying on the fact that DataExtractor was replaced by our test.
UPDATE 24/6:
Here is an updated test:
import unittest
from unittest import mock
from workflow.workflow import Task
#mock.patch("workflow.workflow.DataExtractor")
class TestSomeExtract(unittest.TestCase):
def test_extract_from_snowflake(self, mock_extract):
mock_extract.return_value.extract.return_value = True
actual_result = Task(name="entities", key="11").extract_data()
self.assertTrue(actual_result)
mock_extract.assert_called_once_with() # mock_extract is equal to DataExtractor(), which is called without parameters
A couple of things to notice:
We Test the workflow module, hence we call the initialization of Task and then call extract_data.
We only test workflow module, so we mock other dependencies like the DataExtractor of that module.
I had to do minor changes to Task class. For example, change it to get the key parameter from outside.

Related

Python3 -unittest mocking - problem with isinstance

I have some problem with mocking and testing my function/class.
I think the easiest way to explain it will be showing some code:
module.py:
import some_module
class MyClassToTest:
def __init__():
pass
def f(self, a):
if isinstance(a, some_module.someClass):
print("true")
I want to test my module.py:
test_module.py:
import sys
from unittest import mock
sys.modules['some_module'] = mock.MagicMock() # mocking whole module
def test_my_test():
to_test = MyClassToTest()
to_test.f(5)
Of course here I will have an error:
TypeError: isinstance() arg 2 must be a type or tuple of types
because some_module was mocked.
I really need to mock the "some_module" - it's needed for some other tests.
And yes, I probably should unmock the module just for some tests or maybe mock the isinstance when I'm using it in specific function?
Do you have any idea how to process it?
You are in the right way, you've just forgot to set the object spec. Here is a fully functional example based in your question.
some_module.py
class SomeModule(object):
def method(self):
return True
module.py
import some_module
class MyClass:
def __init__(self):
pass
def f(self, a):
if isinstance(a, some_module.SomeModule):
return True
return False
test_module.py
from unittest import mock
from unittest import main, TestCase
import function
class MyClassTestCase(TestCase):
def test_myclass(self):
m = mock.MagicMock(spec=function.some_module.SomeModule)
mc = function.MyClass()
self.assertEqual(mc.f(1), False)

Mock not seen by pytest

I got a class that describes an entity and am trying to write unit tests that should check if certain fields are defaulted to the correct value. One of those fields uses datetime.now() to set the default state.
I am having trouble trying to mock this call to now() in my test. I am guessing it has to do with my folder structure.
src
classes
MyClass.py
pytests
test_MyClass.py
The MyClass definition:
from datetime import datetime
class MyClass:
def __init__(self):
self.mom = datetime.now()
I am using #mock.patch as follows (inside test_MyClass.py):
import pytest
import sys
from unittest import mock
sys.path.append(r'C:\python-projects\test\src')
from classes.MyClass import MyClass
#mock.patch('classes.MyClass.datetime.now', return_value = 'timestamp', autospec = True)
#pytest.fixture
def myclass():
myclass = MyClass()
yield myclass
def test_has_datetime(myclass):
assert myclass.mom == 'timestamp'
The test ignores the patch.
Try this:
make sure you have __init__.py files in src, classesand pytests directories;
remove sys.path.append(r'C:\python-projects\test\src')
in #mock.patch, replace classes.MyClass.datetime.now with src.classes.MyClass.datetime.now
make C:\python-projects\test\ the current working directory and run pytest ./pytests
Otherwise, mocking the datetime module can be easier with packages like freezegun: https://github.com/spulec/freezegun

How to mock a base_class from an external module?

I'm trying to unit test a class which is derived from a base_class in an external module. In my dev/test environment I have not access to this external module, which means I have to somehow mock this base_class.
My test-code resides in a different file from the code I'm trying to test.
The problem can be summarized as follows:
my_class.py
import external_module
class MyClass(external_module.ExternalClass):
def test_method(self):
return "successful"
test_my_class.py
import sys
import unittest
from unittest.mock import MagicMock
sys.modules['external_module'] = MagicMock()
from my_class import MyClass
class TestMyClass(unittest.TestCase):
def test_first(self):
my_class = MyClass()
result = my_class.test_method()
self.assertEqual(result, "successful")
if __name__ == '__main__':
unittest.main()
Results
When running test_my_class.py the result are the following.
AssertionError: <MagicMock name='mock.ExternalClass.test_method()' id='140272215184664'> != 'successful'
Clearly since the external_module is mocked, even MyClass becomes an instance of a mock-object.
Similar posts
The problem is similar to as described in Python mock: mocking base class for inheritance, but has the difference that the base_class is from an external module.
Even How to mock a base class with python mock library show som similarities to my problem, though the solutions can not be directly applied.
Tries and failures
To get the import
import external_module
to work in my_class.py
sys.modules['external_module'] = MagicMock()
need to be set in test_my_class.py.
Though, this leads to that external_module.* becomes a Mock-instance.
You could create a helper module mocked_external_module, which can be imported from your tests and also contains a class base_class. Then you do the following in your test code:
import mocked_external_module
sys.modules['external_module'] = mocked_external_module
Plus, every method of your base_class that you need to mock you can create as a Mock or MagicMock.

Python Mock Patch multiple methods in a class

Im trying to patch multiple methods in a class. Here is my simplified set up
Hook.py is defined as
class Hook():
def get_key(self):
return "Key"
def get_value(self):
return "Value"
HookTransfer.py defined as
from Hook import Hook
class HookTransfer():
def execute(self):
self.hook = Hook()
key = self.hook.get_key()
value = self.hook.get_value()
print(key)
print(value)
I want to mock the methods get_key and get_value in the Hook class. The following works i.e. prints New_Key and New_Value
from HookTransfer import HookTransfer
import unittest
from unittest import mock
class TestMock(unittest.TestCase):
#mock.patch('HookTransfer.Hook.get_key', return_value="New_Key")
#mock.patch('HookTransfer.Hook.get_value', return_value="New_Value")
def test_execute1(self, mock_get_key, mock_get_value):
HookTransfer().execute()
if __name__ == '__main__':
unittest.main()
However this does not. It prints <MagicMock name='Hook().get_key()' id='4317706896'> and <MagicMock name='Hook().get_value()' id='4317826128'>
from HookTransfer import HookTransfer
import unittest
from unittest import mock
class TestMock(unittest.TestCase):
#mock.patch('HookTransfer.Hook', spec=True)
def test_execute2(self, mock_hook):
mock_hook.get_key = mock.Mock(return_value="New_Key")
mock_hook.get_value = mock.Mock(return_value="New_Value")
HookTransfer().execute()
if __name__ == '__main__':
unittest.main()
Intuitively it seems like the second one should work too but it doesnt. Could you help explain why it does not. I suspect it has something to do with "where to patch" but Im unable to get clarity.
You can patch multiple methods of a module or a class using patch.multiple(). Something like this should work for your case:
import unittest
from unittest.mock import MagicMock, patch
class TestMock(unittest.TestCase):
#patch.multiple('HookTransfer.Hook',
get_key=MagicMock(return_value='New_Key'),
get_value=MagicMock(return_value='New_Value'))
def test_execute1(self, **mocks):
HookTransfer().execute()
When patch.multiple() is used as a decorator, the mocks are passed into the decorated function by keyword, and a dictionary is returned when it's used as a context manager.
What you need to is:
mock the class Hook,
from HookTransfer import HookTransfer
from Hook import Hook
import unittest
try:
import mock
except ImportError:
from unittest import mock
class TestMock(unittest.TestCase):
#mock.patch.object(Hook, 'get_key', return_value="New_Key")
#mock.patch.object(Hook, 'get_value', return_value="New_Value")
def test_execute1(self, mock_get_value, mock_get_key):
HookTransfer().execute()
if __name__ == "__main__":
unittest.main()
After some testing I was able to find the issue.
In the second test case, the patch decorator creates a new instance of a Mock class and passes it via mock_hook argument to test_execute2 function. Lets refer to this as mock1. mock1 replaces the Hook class in HookTransfer.py. When self.hook = Hook() is run, it translates to calling __init__ of mock1. By design this returns yet another Mock instance - lets refer to this as mock2. So self.hook points to mock2. But mock_hook.get_key = mock.Mock(return_value="New_Key"), mocks the methods in mock1.
In order to mock correctly, mock2 needs to be patched. This can be done in 2 ways
By mocking the return_value of mock1 (which returns mock2) mock_hook.return_value.get_key = mock.Mock(return_value="New_Key")
Mocking the return value of constructor of mock1 (which returns mock2) mock_hook().get_key = mock.Mock(return_value="New_Key")
Under the wraps both options really do the same thing.

Python mock object instantiation

Using Python 2.7, and mock library
How can I test that certain patched object has been initialized with some specific arguments using mock?
Here some sample code and pseudo-code:
unittest.py :
import mock
#mock.patch('mylib.SomeObject')
def test_mytest(self, mock_someobject):
test1 = mock_someobject.return_value
test1 = method_inside_someobject.side_effect = ['something']
mylib.method_to_test()
# How can I assert that method_to_test instanced SomeObject with certain arguments?
# I further test things with that method_inside_someobject call, no problems there...
mylib.py :
from someobjectmodule import SomeObject
def method_to_test():
obj = SomeObject(arg1=val1, arg2=val2, arg3=val3)
obj.method_inside_someobject()
So, how can I test SomeObject was instanced with arg1=val1, arg2=val2, arg3=val3?
If you replaced a class with a mock, creating an instance is just another call. Assert that the right parameters have been passed to that call, for example, with mock.assert_called_with():
mock_someobject.assert_called_with(arg1=val1, arg2=val2, arg3=val3)
To illustrate, I've updated your MCVE to a working example:
test.py:
import mock
import unittest
import mylib
class TestMyLib(unittest.TestCase):
#mock.patch('mylib.SomeObject')
def test_mytest(self, mock_someobject):
mock_instance = mock_someobject.return_value
mock_instance.method_inside_someobject.side_effect = ['something']
retval = mylib.method_to_test()
mock_someobject.assert_called_with(arg1='foo', arg2='bar', arg3='baz')
self.assertEqual(retval, 'something')
if __name__ == '__main__':
unittest.main()
mylib.py:
from someobjectmodule import SomeObject
def method_to_test():
obj = SomeObject(arg1='foo', arg2='bar', arg3='baz')
return obj.method_inside_someobject()
someobjectmodule.py:
class SomeObject(object):
def method_inside_someobject(self):
return 'The real thing'
and running the test:
$ python test.py
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK

Categories

Resources