Python - Mocking the behavior of an object outside of a function - python

I have 3 files:
main.py
from util import ResultGetter
result_getter = ResultGetter()
result = result_getter.get_result()
def function_a():
return result
util.py
class ResultGetter:
def get_result(self):
return False
and test_main.py
import main
from _pytest.monkeypatch import MonkeyPatch
def test_function_a():
def get_new_result():
return True
monkeypatch = MonkeyPatch()
monkeypatch.setattr(main.result_getter, "get_result", get_new_result)
result = main.function_a()
assert(result == True)
I'm attempting to override the behavior of get_result with get_new result and make the assertion True. However it's still pulling in the False from util.py.
I am new to mocking in python, so any help would be appreciated.

The problem here is that this block below is set outside the scope of the function :
result_getter = ResultGetter()
result = result_getter.get_result()
I changed the main.py to :
from util import ResultGetter
def function_a():
return ResultGetter().get_result()
And it worked. The problem here is you can't access the value of the object outside the function with the MonkeyPatch or even with the patch function like :
with patch("__main__.ResultGetter.get_result") as get_new_result:
get_new_result.return_value= True
result = function_a()
Since it's set up beforehands your test from the object result_getter.

Related

How to patch a module method that is called within a class?

I have the following structure:
# create.py
import sshHandler
class Create:
def __init__(self):
self.value = sshHandler.some_method()
# sshHandler.py
def some_method():
return True
If I kow try to patch sshHandler.some_method it will not work as expected
from unittest import TestCase
from unittest.mock import patch
import create
class TestCreate(TestCase):
#patch("sshHandler.some_method")
def test_create(self, mock_ssh):
mock_ssh.return_value = False
c = create.Create()
# c.value = True but should be false
The result I am looking for is that some_method would be patched in create as well (and return false). If I just call some_method in the context of test_create it works as expected. How do I fix the patch so that it is also active in the Create class when accessing sshHandler?
I saw this question Why python mock patch doesn't work?, but couldn't solve my problem with the information given there.
You've patched the wrong module. Instead patch the sshHandler.some_method patch create.sshHandler.some_method. You must patch the object of module you're handling.

Pytest - mocking a side_effect on mock's nested attribute function / method

I have a fixture mocking an external library like so, using pytest-mock, which is a wrapper around unittest.mock.
# client.py
import Test as TestLibrary
class LibraryName():
def get_client():
return TestLibrary.Library()
# library_service.py
def using_library():
'''
Edited note: Library().attribute behind the scenes is set to
self.attribute = Attribute()
so this may be affecting the mocking
'''
client = LibraryName.get_client()
return client.attribute.method()
# conftest.py
#pytest.fixture
def library_client_mock(mocker):
import Test as TestLibrary
return mocker.patch.object(TestLibrary, 'Library')
# test_library_service.py
def test_library_method(library_client_mock):
result = using_library()
I can mock a return value like so:
def test_library_method(library_client_mock):
library_client_mock.return_value.attribute.return_value.method.return_value = "test"
result = using_library()
assert result == "test"
but I can't mock throwing an Exception with side_effect
def test_library_method(library_client_mock):
library_client_mock.return_value.attribute.return_value.method.side_effect = TypeError # doesn't work
library_client_mock.return_value.attribute.return_value.method.side_effect = TypeError() # doesn't work
attrs = { 'attribute.method.side_effect': TypeError }
library_client_mock.configure_mock(**attrs) # doesn't work
with pytest.raises(TypeError):
using_library() # fails assertion
what I missing here?
These are the errors in your code:
Change:
library_client_mock.return_value.attribute.return_value.method.return_value = "test"
To:
library_client_mock.return_value.attribute.method.return_value = "test"
Change:
library_client_mock.return_value.attribute.return_value.method.side_effect = TypeError
To:
library_client_mock.return_value.attribute.method.side_effect = TypeError
Explanation
The .return_value must only be used for callable objects e.g. a function as documented:
return_value
Set this to configure the value returned by calling the mock:
>>> mock = Mock()
>>> mock.return_value = 'fish'
>>> mock()
'fish'
Thus, you can use .return_value only for the following:
TestLibrary.Library()
TestLibrary.Library().attribute.method()
But not for:
TestLibrary.Library().attribute
Because .attribute is not a callable e.g. TestLibrary.Library().attribute().
Warning
The way you are patching Library is via its source location at Test.Library (or aliased as TestLibrary.Library). specifically via:
import Test as TestLibrary
return mocker.patch.object(TestLibrary, 'Library')
It works currently because the way you import and use it is via the root path.
# client.py
import Test as TestLibrary
...
return TestLibrary.Library()
...
But if we change the way we imported that library and imported a local version to client.py:
# client.py
from Test import Library # Instead of <import Test as TestLibrary>
...
return Library() # Instead of <TestLibrary.Library()>
...
It will now fail. Ideally, you should patch the specific name that is used by the system under test, which here is client.Library.
import client
return mocker.patch.object(client, 'Library')
Unless you are sure that all files that will use the library will import only the root and not a local version.
#Niel Godfrey Ponciano set me on the right path with this syntax for the side_effect
library_client_mock.return_value.attribute.method.side_effect = TypeError
but it wasn't enough.
In
# conftest.py
#pytest.fixture
def library_client_mock(mocker):
import Test as TestLibrary
return mocker.patch.object(TestLibrary, 'Library')
I had to add an extra mock:
# conftest.py
#pytest.fixture
def library_client_mock(mocker):
import Test as TestLibrary
mock_library_client = mocker.patch.object(TestLibrary, 'Library')
# option 1
mock_attribute = Mock()
# option 2, path to Library.attribute = Attribute()
mock_attribute = mocker.patch.object(TestLibrary.services, 'Attribute', autospec=True)
mock_library_client.attach_mock(mock_attribute, "attribute")
return mock_library_client
and then both of the following statements worked as expected. Although I am not sure why return_value works out of the box without an attached mock, but side_effect does not.
# return_value set correctly
# NOTE return_value needed after each
library_client_mock.return_value.attribute.return_value.method.return_value = "test"
# side_effect set correctly
# NOTE return_value not needed after "attribute"
library_client_mock.return_value.attribute.method.side_effect = TypeError

Python mock a method when specific argument

I have a python method like
import external_object
from external_lib1 import ExternalClass1
from external_lib2 import Hook
class MyClass(self):
def my_method(self):
ExternalClass.get('arg1') #should be mocked and return a specific value with this arg1
ExternalClass.get('arg2') #should be mocked and return a specific value with this arg2
def get_hook(self):
return Hook() # return a mock object with mocked method on it
def my_method(self):
object_1 = external_object.instance_type_1('args') # those are two different object instanciate from the same lib.
object_2 = external_object.instance_type_2('args')
object_1.method_1('arg') # should return what I want when object_1 mocked
object_2.method_2 ('arg') # should return what I want when object_2 mocked
In my test I would like to realise what I put in comments.
I could manage to do it, but every time it gets really messy.
I use to call flexmock for some stuff (by example ExternalClass.get('arg1') would be mock with a flexmock(ExternalClass).should_return('arg').with_args('arg') # etc...) but I'm tired of using different test libs to mock.
I would like to use only the mock library but I struggle to find a consistent way of doing it.
I like to use python's unittest lib. Concretely the unittest.mock which is a great lib to customize side effects and return value in unit tested functions.
They can be used as follows:
class Some(object):
"""
You want to test this class
external_lib is an external component we cannot test
"""
def __init__(self, external_lib):
self.lib = external_lib
def create_index(self, unique_index):
"""
Create an index.
"""
try:
self.lib.create(index=unique_index) # mock this
return True
except MyException as e:
self.logger.error(e.__dict__, color="red")
return False
class MockLib():
pass
class TestSome(unittest.TestCase):
def setUp(self):
self.lib = MockLib()
self.some = Some(self.lib)
def test_create_index(self):
# This will test the method returns True if everything went fine
self.some.create_index = MagicMock(return_value={})
self.assertTrue(self.some.create_index("test-index"))
def test_create_index_fail(self):
# This will test the exception is handled and return False
self.some.create_index = MagicMock(side_effect=MyException("error create"))
self.assertFalse(self.some.create_index("test-index"))
Put the TestSome() class file somewhere like your-codebase-path/tests and run:
python -m unittest -v
I hope it's useful.

Mocking a method outside of a class

I need to write a unit test for credential checking module looks something like below. I apologize I cannot copy the exact code.. but I tried my best to simplify as an example.
I want to patch methodA so it returns False as a return value and test MyClass to see if it is throwing error. cred_check is the file name and MyClass is the class name. methodA is outside of MyClass and the return value checkedcredential is either True or False.
def methodA(username, password):
#credential check logic here...
#checkedcredential = True/False depending on the username+password combination
return checkedcredential
class MyClass(wsgi.Middleware):
def methodB(self, req):
username = req.retrieve[constants.USER]
password = req.retrieve[constants.PW]
if methodA(username,password):
print(“passed”)
else:
print(“Not passed”)
return http_exception...
The unit test I currently have looks like...
import unittest
import mock
import cred_check import MyClass
class TestMyClass(unittest.Testcase):
#mock.patch('cred_check')
def test_negative_cred(self, mock_A):
mock_A.return_value = False
#not sure what to do from this point....
The part I want to write in my unittest is return http_exception part. I am thinking of doing it by patching methodA to return False. After setting the return value, what would be the proper way of writing the unittest so it works as intended?
What you need to do in your unittest to test http_exception return case is:
patch cred_check.methodA to return False
Instantiate a MyClass() object (you can also use a Mock instead)
Call MyClass.methodB() where you can pass a MagicMock as request and check if the return value is an instance of http_exception
Your test become:
#mock.patch('cred_check.methodA', return_value=False, autospec=True)
def test_negative_cred(self, mock_A):
obj = MyClass()
#if obj is a Mock object use MyClass.methodB(obj, MagicMock()) instead
response = obj.methodB(MagicMock())
self.assertIsInstance(response, http_exception)
#... and anything else you want to test on your response in that case
import unittest
import mock
import cred_check import MyClass
class TestMyClass(unittest.Testcase):
#mock.patch('cred_check.methodA',return_value=False)
#mock.patch.dict(req.retrieve,{'constants.USER':'user','constants.PW':'pw'})
def test_negative_cred(self, mock_A,):
obj=MyClass(#you need to send some object here)
obj.methodB()
It should work this way.

Monkey patch a function in a module for unit testing

I have the following method in a module that calls another method imported from another module:
def imported_function():
do_unnecessary_things_for_unittest()
The actual method that needs to be tested, imports and uses the above function:
from somewhere import imported_function
def function_to_be_tested():
imported_function()
do_something_more()
return 42
The inner calls and related calculations inside imported_function are not important and they are not what I want to test so I just want to skip them while testing the actual function function_to_be_tested.
Thus, I tried to monkey patch the module named somewhere inside the test method but no luck.
def test_function_to_be_tested(self):
import somewhere
somewhere.__dict__['imported_function'] = lambda : True
The question is, how can I monkey patch a method of a module while testing so that it will not be called during the test phase?
I think better to use Mock Library
So you can do something like:
from somewhere import imported_function
#patch(imported_function)
def test_function_to_be_tested(self, imported_function):
imported_function.return_value = True
#Your test
I think for unit tests it's better than monkey patch.
Say you have the following files:
somewhere.py
def imported_function():
return False
testme.py
from somewhere import imported_function
def function_to_be_tested():
return imported_function()
A call to testme.function_to_be_tested() would return False.
Now, the trick is to import somewhere before testme:
import somewhere
somewhere.__dict__['imported_function'] = lambda : True
import testme
def test_function_to_be_tested():
print testme.function_to_be_tested()
test_function_to_be_tested()
Output:
True
Or, reload the testme module
import testme
def test_function_to_be_tested():
print testme.function_to_be_tested()
import somewhere
somewhere.__dict__['imported_function'] = lambda : True
print testme.function_to_be_tested()
reload(testme)
print testme.function_to_be_tested()
test_function_to_be_tested()
Output:
False
False
True

Categories