Problems mocking nested classes in DNSResolver - python

I have the following code:
import unittest, mock
class MockedRRData(object):
def to_text(self):
return '0 example.com.'
class MockedResponse(object):
answer = [[MockedRRData()]]
class MockedReturnValue(object):
response = MockedResponse()
class MockedDNSResolver(object):
def query(self, domain_name, query_type):
return MockedReturnValue()
class DNSQueryTest(unittest.TestCase):
def setUp(self):
# MockedRRData = mock.Mock(to_text=lambda: '0 example.com.')
# MockedResponse = mock.Mock(answer=[[MockedRRData()]])
# MockedReturnValue = mock.Mock()
# MockedReturnValue.attach_mock(MockedResponse, 'response')
# MockedReturnValue = mock.Mock(response=MockedResponse())
self.fake_dns_resolver = mock.Mock(query=lambda *args, **kwargs: MockedReturnValue())
def test_mock(self):
for rrset in self.fake_dns_resolver.query('a', 'b').response.answer:
for rrdata in rrset:
print(rrdata.to_text())
unittest.main()
It works, but I'd like to transition to fully using mock classes - as shown in the comments. The problem is that when I uncomment even just the last line of my comments, I get the following:
E
======================================================================
ERROR: test_mock (__main__.DNSQueryTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/a.py", line 33, in test_mock
for rrset in self.fake_dns_resolver.query('a', 'b').response.answer:
TypeError: 'Mock' object is not iterable
----------------------------------------------------------------------
Ran 1 test in 0.000s
FAILED (errors=1)
How do I fix that?

Apparently, I made the mistake of trying to instantiate mocks twice by adding extra () (mock.Mock returns an instance, not a class template). Here's the working code - compare the previously commented part.
import unittest, mock
class MockedRRData(object):
def to_text(self):
return '0 profound.mail.pairserver.com.'
class MockedResponse(object):
answer = [[MockedRRData()]]
class MockedReturnValue(object):
response = MockedResponse()
class MockedDNSResolver(object):
def query(self, domain_name, query_type):
return MockedReturnValue()
class DNSQueryTest(unittest.TestCase):
def setUp(self):
MockedRRData = mock.Mock(to_text=lambda: '0 profound.mail.pairserver.com.')
MockedResponse = mock.Mock(answer=[[MockedRRData]])
MockedReturnValue = mock.Mock(response=MockedResponse)
self.fake_dns_resolver = mock.Mock(query=lambda *args, **kwargs: MockedReturnValue)
def test_mock(self):
for rrset in self.fake_dns_resolver.query('a', 'b').response.answer:
for rrdata in rrset:
print(rrdata.to_text())
unittest.main()

Related

How can I mock an attribute of a class using the mock decorator?

#these classes live inside exchanges/impl/tse/mixins.py
class PacketContext:
capture_tstamp = None
def __init__(self, capture_tstamp=None):
self.capture_tstamp = capture_tstamp
class SubParserMixin():
def __init__(self):
self.context = PacketContext()
def on_packet(self, packet):
self.context.capture_tstamp = packet.capture_timestamp
self.parse_er_data(packet.payload)
#this mock test lives in another python file
from exchanges.impl.tse.mixins import PacketContext
#patch.object(PacketContext, 'capture_tstamp', 1655417400314635000)
def test_receive_timestamp(self):
"""
test receive_timestamp is passed down correctly from PacketContext to on_packet()
"""
assert self.context.capture_tstamp == 1655417400314635000
I am trying to mock the self.capture_tstamp attribute in the PacketContext() class.
But in the above, I am getting an error that says
AssertionError: assert None == 1655417400314635000
E + where None = <exchanges.impl.tse.mixins.PacketContext object at 0x7fb324ac04c0>.capture_tstamp
E + where <exchanges.impl.tse.mixins.PacketContext object at 0x7fb324ac04c0> = <tests.unit.exchanges.tse.test_quote_write.TestTSE testMethod=test_receive_timestamp>.context
It seems very strange that the program is not recognising PacketContext().
You can make use of the patch.object decorator as below
class PacketContext:
capture_tstamp = None
def __init__(self, capture_tstamp=None):
self.capture_tstamp = capture_tstamp
<import_PacketContext_here>
#patch.object(PacketContext, 'capture_tstamp', 1655417400314635000)
def test_receive_timestamp():
test_instance = PacketContext()
assert test_instance.capture_tstamp == 1655417400314635000

Not sure why MyMock.env["key1"].search.side_effect=["a", "b"] works but MyMock.env["key1"] = ["a"] with MyMock.env["key2"] = ["b"] does not work

I had created a simple example to illustrate my issue. First is the setup say mydummy.py:
class TstObj:
def __init__(self, name):
self.name = name
def search(self):
return self.name
MyData = {}
MyData["object1"] = TstObj("object1")
MyData["object2"] = TstObj("object2")
MyData["object3"] = TstObj("object3")
def getObject1Data():
return MyData["object1"].search()
def getObject2Data():
return MyData["object2"].search()
def getObject3Data():
return MyData["object3"].search()
def getExample():
res = f"{getObject1Data()}{getObject2Data()}{getObject3Data()}"
return res
Here is the test that failed.
def test_get_dummy1():
dummy.MyData = MagicMock()
mydummy.MyData["object1"].search.side_effect = ["obj1"]
mydummy.MyData["object2"].search.side_effect = ["obj2"]
mydummy.MyData["object3"].search.side_effect = ["obj3"]
assert mydummy.getExample() == "obj1obj2obj3"
The above failed with run time error:
/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/mock.py:1078: StopIteration
Here is the test that passed:
def test_get_dummy2():
dummy.MyData = MagicMock()
mydummy.MyData["object1"].search.side_effect = ["obj1", "obj2", "obj3"]
assert mydummy.getExample() == "obj1obj2obj3"
Am I missing something? I would have expected test_get_dummy1() to work and test_get_dummy2() to fail and not vice versa. Where and how can I find/learn more information about mocking to explain what is going on...
MyData["object1"] is converted to this function call: MyData.__getitem__("object1"). When you call your getExample method, the __getitem__ method is called 3 times with 3 parameters ("object1", "object2", "object3").
To mock the behavior you could have written your test like so:
def test_get_dummy_alternative():
mydummy.MyData = MagicMock()
mydummy.MyData.__getitem__.return_value.search.side_effect = ["obj1", "obj2", "obj3"]
assert mydummy.getExample() == "obj1obj2obj3"
Note the small change from your version: mydummy.MyData["object1"]... became: mydummy.MyData.__getitem__.return_value.... This is the regular MagicMock syntax - we want to to change the return value of the __getitem__ method.
BONUS:
I often struggle with mock syntax and understanding what's happening under the hood. This is why I wrote a helper library: the pytest-mock-generator. It can show you the actual calls made to the mock object.
To use it in your case you could have added this "exploration test":
def test_get_dummy_explore(mg):
mydummy.MyData = MagicMock()
mydummy.getExample()
mg.generate_asserts(mydummy.MyData, name='mydummy.MyData')
When you execute this test, the following output is printed to the console, which contains all the asserts to the actual calls to the mock:
from mock import call
mydummy.MyData.__getitem__.assert_has_calls(calls=[call('object1'),call('object2'),call('object3'),])
mydummy.MyData.__getitem__.return_value.search.assert_has_calls(calls=[call(),call(),call(),])
mydummy.MyData.__getitem__.return_value.search.return_value.__str__.assert_has_calls(calls=[call(),call(),call(),])
You can easily derive from here what has to be mocked.

How to run unit tests in python?

I am having a difficult time understanding how to use assertions in unit tests in Python.
Original code:
class A:
def method(file):
if file:
<do something>
else:
raise Exception("file not found")
Now to create its test. Let's say I don't want to pass a file and test it.
t1 = A()
class Test(TestCase):
def test_method_no_path(self):
t1.method(' ') #passed no file
<Now do what> ??
self.assert ??
# tests/test_ex.py
from os import path
from unittest import TestCase
class Error(Exception):
pass
class FileChecker:
def process_file(self, f_path: str):
if path.exists(f_path) and path.isfile(f_path):
# just an example
return 'result'
raise Error(f_path)
class TestFileChecker(TestCase):
_CHECKER = FileChecker()
def test_done(self):
# check method result
self.assertTrue(self._CHECKER.process_file('/tmp/1.txt') == 'result')
def test_error(self):
# check method exception
with self.assertRaises(Error):
self._CHECKER.process_file('/tmp/1.txt')
Run our test(nosetests tests/test_ex.py). test_done failed because /tmp/1.txt file does not exist:
======================================================================
ERROR: test_done (tests.test_ex.TestFileChecker)
Let's create a file(echo 'test' >> /tmp/1.txt) and run test one more time:
======================================================================
FAIL: test_error (tests.test_ex.TestFileChecker)
As you can see test_done works fine, because we got expected result, but now we have problems with test_error(because Error wasn't raised).

Set attribue in custom class derived from uuid.UUID: AttributeError

I'm porting some code from IronPython to CPython (3.8) + Python.NET and I have a custom class that's broken in an odd way: it gives me an AttributeError even though the member is present in __dict__. This class derives from uuid.UUID to add support to BLE short UUIDs:
import uuid
class UUID(uuid.UUID):
"""Represents a more general version of the uuid.UUID, so we can have both
regular and short UUIDs.
"""
def __init__(self, hex=None, bytes=None, is_short=False, **kwargs):
try:
super(UUID, self).__init__(hex=hex, bytes=bytes, **kwargs)
self.__dict__['is_short'] = False
except ValueError as val_ex:
if hex is None or len(hex) != 4:
raise val_ex
# Remove braces GUIDs
hex_digits = hex.strip('{}').replace('-', '')
# remove RFC 4122 URN's 'urn:uuid:deadbeef-1234-fedc-5678-deadbeefaaaa'
hex_digits = hex_digits.replace('uuid:', '').replace('urn:', '')
if len(hex_digits) != 4:
raise ValueError('badly formed hexadecimal UUID string')
self.__dict__['int'] = int(hex, 16)
self.__dict__['is_short'] = True
if is_short is True:
self.__dict__['is_short'] = True
def __str__(self):
if self.is_short:
hex = '%04x' % self.int
return '%s' % (hex[:4])
else:
return super(UUID, self).__str__()
And I'm using it like so:
class Test_UUID(unittest.TestCase):
def test_create(self):
x = pybletestlibrary.UUID("1800")
pprint(x.__dict__)
print(x)
The above code yields:
{'int': 6144, 'is_short': True}
E
======================================================================
ERROR: test_create (__main__.Test_UUID)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test\test_pybletestlibrary.py", line 27, in test_create
print(x)
File "my_uuid.py", line 66, in __str__
hex = '%04x' % self.int
AttributeError: int
----------------------------------------------------------------------
Ran 1 test in 0.003s
FAILED (errors=1)
I can't use setattr() because uuid.UUID is immutable. I don't understand why the AttributeError as int is present in __dict__. Any suggestions?
Thanks!!
Here's what I mean:
import uuid
class UUID(uuid.UUID):
"""Represents a more general version of the uuid.UUID, so we can have both
regular and short UUIDs.
"""
def __init__(self, hex=None, bytes=None, is_short=False, **kwargs):
self.is_short = False
if len(hex) == 4:
self.is_short = True
hex = "0000"+hex+"-0000-1000-8000-00805f9b34fb"
super(UUID, self).__init__(hex=hex, bytes=bytes, **kwargs)
def __str__(self):
val = super(UUID, self).__str__()
if self.is_short:
return val[4:8]
else:
return val
You might have to override some of the other attributes, too.

Using decorator in a class in python

For understanding decorators in Python, i created in a class an example. But when i run it i receive an error.
class Operation:
def __init__(self, groupe):
self.__groupe = groupe
#property
def groupe(self):
return self.__groupe
#groupe.setter
def groupe(self, value):
self.__groupe = value
def addition(self, func_goodbye):
ln_house = len('house')
ln_school = len('school')
add = ln_house + ln_school
print('The result is :' + str(add))
return func_goodbye
#addition
def goodbye(self):
print('Goodbye people !!')
if __name__ == '__main__':
p1 = Operation('Student')
p1.goodbye()
I receive this error :
Traceback (most recent call last):
File "Operation.py", line 1, in
class Operation:
File "Operation.py", line 21, in Operation
#addition
TypeError: addition() missing 1 required positional argument: 'func_goodbye'
You can have a class scoped decorator, however there won't be a self when the decorator is called
a decorator:
#foo
def bar(): ...
is roughly equivalent to
def bar(): ...
bar = foo(bar)
in your particular example, if you remove the self parameter, it should function as you expect:
def addition(func_goodbye):
ln_house = len('house')
ln_school = len('school')
add = ln_house + ln_school
print('The result is :' + str(add))
return func_goodbye
#addition
def goodbye(self):
print('Goodbye people !!')
for good measure, I might del addition after that just to ensure it isn't accidentally called later
(an aside: one unfortunate side-effect of this is many linters and type checkers will consider this "odd" so I've yet to find a way to appease them (for example mypy))

Categories