How to properly mock private members of a class - python

I am trying to write some unit tests for a method that depends on another private method. - As shown in the example below:
def is_member_of(self, group_name):
members = self.__get_group_members(group_name)
The private method that I'd like to mock is __get_group_members; I'd also like to mock the private attribute __user_id since it will be used in the is_member_of function (not shown in the example above).
What I have so far:
import unittest
from unittest import mock
class Test(unittest.TestCase):
group_data = []
user_id = 'test_user_id'
def mock_dependencies(self, x):
x.__user_id = mock.PropertyMock(return_value=self.user_id)
x.__get_group_members = mock.MagicMock(return_value=self.group_data)
def first_test(self):
x = A(('name', 'group'))
self.mock_dependencies(x)
x.is_member_of('test_group')
When I invoke x.is_member_of() the mocking doesn't work as anticipated.

You can access a private attribute in Python since private and protected are by convention. - What you're looking for is basically using _ClassName__private_attribute_name since, python carries out the renaming in order to achieve the convention agreed upon.
Example (returning a MagicMock):
with mock.patch.object(Class, '_ClassName__private_attribute_name', return_value='value') as obj_mock:
pass
Example (returning a raw value):
with mock.patch.object(Class, '_ClassName__private_attribute_name', new_callable=PropertyMock) as obj_mock:
obj_mock.return_value = 'string value'
Class is a reference to the class itself - not the instance.
Complete Example:
from unittest.mock import patch, PropertyMock
from unittest import TestCase, main
class Private:
__attr = 'hello'
class PrivateTest(TestCase):
#patch.object(Private, '_Private__attr', new_callable=PropertyMock)
def test_private_attribute_value_change_decorator_success(self, private_mock):
obj = Private()
private_mock.return_value = 'string'
self.assertEqual('string', obj._Private__attr)
def test_private_attribute_value_change_context_manager_success(self):
with patch.object(Private, '_Private__attr', new_callable=PropertyMock) as o_mock:
obj = Private()
o_mock.return_value = 'mocked value'
self.assertEqual('mocked value', obj._Private__attr)
if __name__ == '__main__':
main()
Modifications to your example:
from unittest import TestCase, mock, main
class A:
__user_id = 3
def __init__(self, user, group):
"""
Your logic is missing - obviously
:param user:
:param group:
"""
def __get_group_members(self):
"""
Your logic is missing - obviously
:return:
"""
return ['user_1', 'user_2']
def is_member_of(self, group_name):
members = self.__get_group_members(group_name)
# will return if the user is a member of the group
return self.__user_id in members
class GroupTest(TestCase):
group_data = [1, 2]
user_id = 'test_user_id'
#mock.patch.object(A, '_A__get_group_members')
#mock.patch.object(A, '_A__user_id', new_callable=mock.PropertyMock)
def test_this_is_my_first_success(self, user_id_mock: mock.PropertyMock, get_group_members_mock: mock.MagicMock):
get_group_members_mock.return_value = self.group_data
user_id_mock.return_value = 3
x = A('user_3', 'this_group')
self.assertEqual(False, x.is_member_of('test_group'))
#mock.patch.object(A, '_A__get_group_members')
#mock.patch.object(A, '_A__user_id', new_callable=mock.PropertyMock)
def test_this_is_my_first_failure(self, user_id_mock: mock.PropertyMock, get_group_members_mock: mock.MagicMock):
get_group_members_mock.return_value = self.group_data
user_id_mock.return_value = 1
x = A('user_1', 'this_group')
self.assertEqual(True, x.is_member_of('test_group'))
if __name__ == '__main__':
main()
If you know you'll mock these two attributes in all test cases you can add the decorators on the class level and expect the arguments like-wise.
In the case where the attribute is set through the __init__ or any other method, you could simply alter it as shown below.
from unittest import TestCase, mock, main
class A:
def __init__(self, user, group):
"""
Your logic is missing - obviously
:param user:
:param group:
"""
def __get_group_members(self):
"""
Your logic is missing - obviously
:return:
"""
return ['user_1', 'user_2']
def is_member_of(self, group_name):
members = self.__get_group_members(group_name)
# will return if the user is a member of the group
return self.__user_id in members
class GroupTest(TestCase):
group_data = [1, 2]
user_id = 'test_user_id'
#mock.patch.object(A, '_A__get_group_members')
def test_this_is_my_first_success(self, get_group_members_mock: mock.MagicMock):
x = A('user_3', 'this_group')
x._A__user_id = 5
get_group_members_mock.return_value = self.group_data
self.assertEqual(False, x.is_member_of('test_group'))
#mock.patch.object(A, '_A__get_group_members')
def test_this_is_my_first_failure(self, get_group_members_mock: mock.MagicMock):
get_group_members_mock.return_value = self.group_data
x = A('user_1', 'this_group')
x._A__user_id = 1
self.assertEqual(True, x.is_member_of('test_group'))
if __name__ == '__main__':
main()

Related

How to import function or some config codes from class to other class in same file python?

I have this codes python
image_formats = [('xz','xz'), ('gz','gz')]
config.image_format = ConfigSelection(default = "xz", choices = image_formats)
mounted_devices = getmDevices()
class1(Screen):
def __init__(self, session):
config.device_path = ConfigSelection(choices = mounted_devices)
self.createSetup()
def createSetup(self):
self.list.append(getConfigListEntry(('Path to store Full Backup'), config.device_path
self.list.append(getConfigListEntry(('Select Format to Compress BackUp'), config.image_format
self['config'].list = self.list
self['config'].l.setList(self.list)
def configsSave(self):
for x in self['config'].list:
x[1].save()
configfile.save()
class2(Screen):
def __init__(self, session):
self.doBackUp()
def doBackUp(self, target):
self.configsSave()
image_name = target
device_path = self['config'].list[0][1].getText()
print("device_path ****************", device_path)
image_formats = self['config'].list[1][1].getText()
print("image_formats ****************", image_formats)
And I have got this error
self.configsSave()
~~~~^^^^^^^^^^
KeyError: 'configsSave'
device_path = self['config'].list[0][1].getText()
~~~~^^^^^^^^^^
image_formats = self['config'].list[0][1].getText()
~~~~^^^^^^^^^^
KeyError: 'config'
So How can I import from class1 complete configsSave function and config list from createSetup function to class2 and solve the error ?!!
If you want both class to share mothod you can tell class2 to inherit from class1 one with this syntax:
class class1(Screen):
enter code here
# class1 one declaration
# Making class 2 inherit from class one
class class2(class1):
super().__init__()
def __init__(self, session):
self.doBackUp()
def doBackUp(self, target):
self.configsSave()
image_name = target
device_path = self['config'].list[0][1].getText()
print("device_path ****************", device_path)
image_formats = self['config'].list[1][1].getText()
print("image_formats ****************", image_formats)
After that you can use class1 methods on class2 objects.

Intercept function call with mock without pytest/unittest/etc

I want to avoid using a test runner such as pytest because of slow test discovery and inconvenient logging output in lieu of setting up my test and calling it as a script however I am having trouble intercepting a function call within my test objects and amending its behavior. I have been able to do this in the past with pytest but I can't seem to get access to the function the way I have things setup. Side note: In part, this is an attempt to mock out a call to a RESTful server which should explain why I am interested in getting this to work.
Here is my sample application structure:
# entry_object.py
import inner_object, time
class EntryObject():
def __init__(self, client):
self.inner_obj = inner_object.InnerObject(client)
pass
def time_injector(self, posix_time : int):
self.inner_obj.doit(posix_time)
pass
def the_time_injector(length_in_secs, client) -> None:
entryobj = EntryObject(client)
i = 0;
posix_time = time.time()
while (i < length_in_secs):
entryobj.time_injector(posix_time+i)
i += 1
# inner_object.py
import pretendapi
class InnerObject():
def __init__(self, client):
self.pretend_api_object = pretendapi.PretendApi(client)
def doit(self, posix_time : int) -> None:
logmessage = self.pretend_api_object.call_api_endpoint(posix_time=posix_time)
# pretendapi.py
import bravado
class PretendApi():
def __init__(self, client=None):
print("Created PretendApi")
self.client = bravado.client.SwaggerClient(None) if client is None else client
self.ex = "pretendapi"
def extract_and_verify_apiresult(self, posix_time : int, apiresult : object) -> (bool, object):
return (True, apiresult[0])
def call_api_endpoint(self, posix_time : int) -> object:
future = self.client.Api.ApiEndpoint()
(is_cmd_success, apiresult) = self.extract_and_verify_apiresult(posix_time, future.result())
if not is_cmd_success:
# print(f"is_cmd_success={dollup(is_cmd_success)}")
return is_cmd_success
# print(f"{apiresult}")
return is_cmd_success
# test_entry_object.py
rom bravado.testing.response_mocks import BravadoResponseMock
import mock
import entry_object, pretendapi
def amend_extract_and_verify_apiresult(original_func):
def amended_func(self, posix_time : int, apiresult : object) -> (bool, object):
print(f"{posix_time} {apiresult[0]['ret_msg']}")
return original_func(self, posix_time, object)
def test_time_injector():
# mock the necessary objects
# setup up the unit under test
# run the test
mock_client = mock.Mock(name='mock SwaggerClient')
mock_client.Api.ApiEndpoint.return_value = \
BravadoResponseMock(result=lambda : \
({'ret_code': 0, 'ret_msg': 'OK', 'ext_code': '', 'ext_info': ''}, '<bravado.requests_client.RequestsResponseAdapter object at 0x0000028385176518>')
)
mock.patch.object(pretendapi.PretendApi, 'extract_and_verify_apiresult',
side_effect=amend_extract_and_verify_apiresult(
pretendapi.PretendApi.extract_and_verify_apiresult),
autospec=True) # Needed for the self argument
entry_object.the_time_injector(10, mock_client)
if __name__ == "__main__":
test_time_injector()
Does anybody understand why I can't seem to decorate the behavior of extract_and_verify_apiresult?

Pytest Mock AWS SecurityManager

my project has a file called config.py which has, among others, the following code:
class Secret(Enum):
DATABASE_A = 'name_of_secret_database_A'
DATABASE_A = 'name_of_secret_database_A'
def secret(self):
if self.value:
return get_secret(self.value)
return {}
def get_secret(secret_name):
session = Session()
client = session.client(
service_name='secretsmanager',
region_name='us-east-1',
)
secret_value = client.get_secret_value(SecretId=secret_name)
return loads(secret_value.get('SecretString', "{}"))
I need to somehow mock get_secret in tests with pytest for all enum calls, for example Secret.DATABASE_A.secret ()
You can use monkeypatch to override the behaviour of get_secret(). I have made the get_secret() method a static method of the Secret class, but you can make it part of any module you want and import it as well. Just make sure you change in in the monkeypatch.setattr() call as well.
import pytest
from enum import Enum
class Secret(Enum):
DATABASE_A = 'name_of_secret_database_A'
DATABASE_B = 'name_of_secret_database_B'
def secret(self):
if self.value:
return Secret.get_secret(self.value)
return {}
#staticmethod
def get_secret(secret_name):
session = Session()
client = session.client(
service_name='secretsmanager',
region_name='us-east-1',
)
secret_value = client.get_secret_value(SecretId=secret_name)
return loads(secret_value.get('SecretString', "{}"))
def test_secret_method(monkeypatch):
def get_secret(secret_name):
return "supersecret"
monkeypatch.setattr(Secret, "get_secret", get_secret)
s = Secret.DATABASE_A
assert s.secret() == "supersecret"
This returns into 1 passed test.
What is happening here is, that I created a function get_secret() in my test_secret_method as well, and then overwrite the Secret.get_secret() with that new method. Now, you can use the Secret class in your test_method and be sure what the 'get_secret()' method will return without actually running the original code.

python-sphinx extension, how to get name of current object?

How to get the name of parent object in Python code for which is current documentation build for? I mean how to get name of class "ExampleCls0" in MyDirective.run()?
class ExampleCls0():
"""
.. mydirect::
"""
Lets suppose that we have Spring directive called mydirect.
And it is correctly registered in Sphinx and documentation is build for python code.
class MyDirective(Directive):
required_arguments = 0
optional_arguments = 0
has_content = True
option_spec = {}
def run(self):
env = self.state.document.settings.env
def setup(app):
app.add_directive('mydirect', MyDirective)
For build I am using:
from sphinx.cmdline import main as sphinx_main
from sphinx.ext.apidoc import main as apidoc_main
apidoc_main(["--module-first", "--force", "--full",
"--output-dir", "doc/", "."])
sphinx_main(["-b", "html", "-E",
"-c", pwd,
"doc/",
"doc_build/",
])
I do not know if name of the parent object can be accessed somewhere in Directive.run method, but I found out that it is possible to read the name later.
class SchematicLink(nodes.TextElement):
#staticmethod
def depart_html(self, node):
self.depart_admonition(node)
#staticmethod
def visit_html(self, node):
parentClsNode = node.parent.parent
assert parentClsNode.attributes['objtype'] == 'class'
assert parentClsNode.attributes['domain'] == 'py'
sign = node.parent.parent.children[0]
assert isinstance(sign, desc_signature)
absoluteName = sign.attributes['ids'][0]
print(absoluteName) # file0.ExampleCls0
self.visit_admonition(node)
class MyDirective(Directive):
required_arguments = 0
optional_arguments = 0
def run(self):
schema_node = SchematicLink()
self.state.nested_parse(self.content,
self.content_offset,
schema_node)
return [schema_node]
def setup(app):
app.add_node(SchematicLink,
html=(SchematicLink.visit_html,
SchematicLink.depart_html))
app.add_directive('mydirect', MyDirective)
And this is probably good example how NOT to do it. Code reads id from label of class doc.

How to access the py.test capsys from inside a test?

py.test documentations says that I should add capsys parameter to my test methods but in my case this doesn't seem to be possible.
class testAll(unittest.TestCase):
def setUp(self):
self.cwd = os.path.abspath(os.path.split(inspect.getfile(inspect.currentframe()))[0])
os.chdir(self.cwd)
def execute(self, cmd, result=0):
"""
Helper method used by many other tests, that would prevent replicating too much code.
"""
# cmd = "%s > /dev/null 2>&1" % cmd
ret = os.system(cmd) >> 8
self.assertEqual(ret, result, "`%s` returned %s instead of %s (cws=%s)\n\t%s" % (cmd, ret, result, os.getcwd(), OUTPUT)) ### << how to access the output from here
def test_1(self):
self.execute("do someting", 0)
You could define a helper function in the class that inherits the capsys fixture:
#pytest.fixture(autouse=True)
def capsys(self, capsys):
self.capsys = capsys
Then call this function inside the test:
out,err = self.capsys.readouterr()
assert out == 'foobar'
Kudos to MichaƂ Krassowski for his workaround which helped me work through a similar problem.
https://github.com/pytest-dev/pytest/issues/2504#issuecomment-309475790
Thomas Wright's answer is perfect. I'm just sticking this code block here for my own reference as my search led me here and I'll likely forget this in future! [doing a few things in this so useful reference for me]. If anyone is looking and sees where it can be improved - suggest away!
import os
import pytest
from _pytest.monkeypatch import MonkeyPatch
from unittest import TestCase
# -----------------------------------------------------------------------------
def foo_under_test(inp1):
"""Example of a Method under test"""
do_some_calcs_here = inp1*2
get_a_return = ClassCalled.foo_called(do_some_calcs_here)
return get_a_return
# -----------------------------------------------------------------------------
class ClassUnderTest():
"""Example of a Class contained Method under test"""
def __init__(self):
"""Instantiate the class"""
self.var1 = "TestVar"
def foo_under_test2(self, inp11):
"""The class method under test"""
return self.var1 + self.foo_called2(inp11)
def foo_called2(self, inp12):
"""Nominal sub-foo to foo_under_test2"""
return str(inp12*5)
# -----------------------------------------------------------------------------
class ClassCalled:
"""Example of a class that could be called by foo_under_test"""
def foo_called(inp2):
"""Sub-foo to foo_under_test"""
return inp2 * 2
# -----------------------------------------------------------------------------
class MockResponses:
"""Class for holding the mock responses"""
def foo_called(inp2):
"""**Mock of foo_called**"""
return inp2*3
def foo_called2(inp12):
"""**Mock of foo_called**"""
return str(inp12*4)
# -----------------------------------------------------------------------------
class Test_foo_under_test(TestCase):
"""Test class - means of grouping up tests for a target function
This one is addressing the individual function (not within a class)
"""
# ---------------------------------------------------------------
#pytest.fixture(autouse=True)
def capsys(self, capsys):
"""Capsys hook into this class"""
self.capsys = capsys
def print_to_console(self, strOut):
"""Print strOut to console (even within a pyTest execution)"""
with self.capsys.disabled():
print(strOut)
def setUp(self):
"""Ran by pyTest before running any test_*() functions"""
self.monkeypatch = MonkeyPatch()
# ---------------------------------------------------------------
def test_1(self):
"""**Test case**"""
def mock_foo_called(inp2):
return MockResponses.foo_called(inp2)
mockedFoo = ClassCalled.foo_called # Need to get this handle here
self.monkeypatch.setattr(ClassCalled, "foo_called", mock_foo_called)
x = foo_under_test(1)
self.print_to_console("\n")
strOut = "Rtn from foo: " + str(x)
self.print_to_console(strOut)
assert x == 6
# Manually clear the monkey patch
self.monkeypatch.setattr(
ClassCalled, "foo_called", mockedFoo)
"""I've noticed with me having monkeypatch inside the
class, the damn thing persists across functions.
This is the only workaround I've found so far"""
# -----------------------------------------------------------------------------
class Test_ClassUnderTest_foo_under_test(TestCase):
"""Test class - means of grouping up tests for a target function
This one is addressing the function within a class
"""
# ---------------------------------------------------------------
#pytest.fixture(autouse=True)
def capsys(self, capsys):
"""Capsys hook into this class"""
self.capsys = capsys
def print_to_console(self, strOut):
"""Print strOut to console (even within a pyTest execution)"""
with self.capsys.disabled():
print(strOut)
def setUp(self):
"""Ran by pyTest before running any test_*() functions"""
self.monkeypatch = MonkeyPatch()
# ---------------------------------------------------------------
def test_1(self):
"""**Test case**"""
def mock_foo_called2(self, inp2):
"""
Mock function
Defining a mock function, note this can be dealt with directly
here, or if its more comprehensible, put it in a separate class
(i.e. MockResponses)
"""
# return MockResponses.foo_called2(inp2) # Delegated approach
return str(inp2*4) # Direct approach
"""Note that the existence of self within this test class forces
a wrapper around calling a MockClass - so we have to go through
both the line below and the function mock_foo_called2() above to
properly invoke MockResponses.foo_called2()
"""
mockedFoo = ClassUnderTest.foo_called2
self.monkeypatch.setattr(
ClassUnderTest, "foo_called2", mock_foo_called2)
x = ClassUnderTest().foo_under_test2(1)
strOut = "Rtn from foo: " + str(x)
self.print_to_console("\n")
self.print_to_console(strOut)
assert x == "TestVar" + str(4)
self.monkeypatch.setattr(
ClassUnderTest, "foo_called2", mockedFoo)
# -----------------------------------------------------------------------------
# ---- Main
if __name__ == "__main__":
#
# Setup for pytest
outFileName = os.path.basename(__file__)[:-3] # Remove the .py from end
currScript = os.path.basename(__file__)
# -------------------------------------------------------------------------
# PyTest execution
pytest.main([currScript, "--html", outFileName + "_report.html"])
rtnA = foo_under_test(1)
print(rtnA == 4)
# This should output 4, demonstrating effect of stub (which produced 6)
rtnB = ClassUnderTest().foo_under_test2(1)
print(rtnB == "TestVar"+str(5))
# This should output "TestVar5", demonstrating effect of stub
# conftest.py
class TTY:
def communicate(self):
with self.trace():
print('wow!')
#pytest.fixture(autouse=True)
def set_capsys(capsys):
TTY.trace = capsys.disabled
#pytest.fixture
def get_tty():
_get_tty():
return TTY()
return _get_tty
# test_wow.py
def test_wow(get_tty):
get_tty().communicate()

Categories

Resources