How to test if a method is called with a particular parameter? - python

I'm writing unit tests for the following the following class.
module1/node_base.py
#dataclass
class NodeBase(metaclass=ABCMeta):
account: AccountBase
name: str
ec2_id: str = ''
def start(self):
self.account.session.resource('ec2').instances.filter(
InstanceIds=[self.ec2_id]).start()
self.account.session is AWS session. How to write the test function? (to check if filter() is called with parameter InstanceIds=[self.ec2_id] and start() is called?)
test_start.py
from module1.node_base import NodeBase
class Node(NodeBase):
'''Node'''
#pytest.fixture
def sut() -> Node:
session = Mock()
def get_resource():
instances = Mock()
def get_filter(filters):
def get_start():
pass
return get_start
instances.filter = get_filter
return instances
session.resource = get_resource()
account = Mock()
account.session = session
return Node(account=account, name='Test')
def test_start(sut):
sut.start()
assert sut.account.session.resource('ec2').instances.filter.call_count == 1
assert sut.account.session.resource('ec2').instances.filter().start.call_count == 1
It seems very verbose to setup the fixture. Is it a simpler approach to test it?
How to test if filter() is called with a particular parameter ['some_id']?

You can use patch to assert whether it is being called with a particular parameter. For example:
#patch('path.to.filter.function')
def func1(mock_filter): // Test case for filter function
id = '1234'
parameter = '1234'
filter(id) // Calling the function here
mock_filter.assert_called_with(parameter)
Here, for the example I'm calling the filter function with value '1234', and later checking if it was called with the same value('1234') or not.
It will return true if the function is called with the parameter you have defined.

Related

override a method of a class with an method from another class

I have following code:
class SomeClass:
def __init__(self) -> None:
pass
def some_class_function(self, par):
print(par)
class SomeOtherClass:
def __init__(self) -> None:
pass
def some_other_class_function(self, par):
print(par+1)
if __name__ == "__main__":
sc = SomeClass()
sc.some_class_function = SomeOtherClass.some_other_class_function
sc.some_class_function(1)
When I execute the code I get
TypeError: some_other_class_function() missing 1 required positional argument: 'par'
How can I override the method of the first class with the method of the second class properly?
As you have noted in the comments, you are interested in adding method that will use sc as the "self" instance.
To that end, see this post. To summarize, you can either add a function to the class definition (affecting future instances of the same class), or bind the function to the particular instance.
As an example, consider the following class and function.
class Test():
def __init__(self):
self.phrase = "hello world"
def func(self):
print("this is the old method")
def test_func(self):
print(self.phrase)
For the first approach, we could do the following
test = Test()
Test.func = test_func
test.func()
Note that future instances of Test will have this function as an attribute. For example, running Test().func() will still result in the same output, even though the method is being used on a new class instance.
For the second, we could do the following.
import types
test = Test()
test.func = types.MethodType(test_func, test)
test.func()
In this case, running the line Test().func() will result in the output "this is the old method" because func has not been overwritten for new instances.
You need to initialize the class to call its method.
sc = SomeClass()
sco = SomeOtherClass() # initialize the second call to call it's method
sc.some_class_function = sco.some_other_class_function
sc.some_class_function(1)

Python mock returns invalid value

I have the following structure:
sources/
- parser/
- sources_parser.py # SourcesParser class is here
- tests/
- test_sources_parsers.py # SourcesParserTest is here
sources_parser.py:
from sources.parser.sitemap_parser import SitemapParser
class SourcesParser(object):
__sitemap_parser: SitemapParser
def __init__(self) -> None:
super().__init__()
self.__sitemap_parser = SitemapParser()
def parse(self):
# ...
urls = self.__parse(source)
self.logger.info(f'Got {len(urls)} URLs')
def __parse(self, source: NewsSource) -> List[str]:
results = self.__sitemap_parser.parse_sitemap(source.url)
self.logger.info(f'Results: {results}, is list: {isinstance(results, list)}')
return results
Test:
class SourcesParserTest(TestCase):
#patch('sources.parser.sources_parser.SitemapParser')
def test_normal_parse(self, mock_sitemap_parser):
mock_sitemap_parser.parse_sitemap.return_value = [
'https://localhost/news/1',
'https://localhost/news/2',
'https://localhost/news/3',
]
parser = SourcesParser()
parser.parse()
And the logs are:
Results: <MagicMock name='SitemapParser().parse_sitemap()' id='5105954240'>, is list: False
Got 0 URLs
If I understand mocking correctly, the parse_sitemap call should return the given list of URLs, but instead it returns a Mock object which is converted to an empty list.
Python version is 3.9.
What's wrong?
If mocking a member function, you have to mock the function on the mocked instance object, not the function on the mocked class object. This may not be completely intuitive, because a member function belongs to a class, but you may think about it as the bound versus unbound method - you have to mock the bound method.
With Mock, mocking an instance is done by using return_value on the class object, similar to mocking the result of a function call, so in your case you need:
#patch('sources.parser.sources_parser.SitemapParser')
def test_normal_parse(self, mock_sitemap_parser):
mocked_parser = mock_sitemap_parser.return_value # mocked instance
mock_parser.parse_sitemap.return_value = [
'https://localhost/news/1',
'https://localhost/news/2',
'https://localhost/news/3',
]
...
(split the mock for illustration only)

Passing an argument to Patch inside a Pytest fixture, from within a test

This is my fixture which returns a Foo object. I patch Foo's internal config variable, then instantiate the class with a parameter.
#pytest.fixture()
def foo_fix():
patch(Foo.config, "hello"):
def wrapper(parameter):
return Foo(parameter=parameter)
yield wrapper
In my test I do:
def test_foo_1(foo_fix):
foo = foo_fix(parameter=1)
assert foo.go() == "abc"
I would like to vary the Foo.config value from inside the test function. I tried nesting the foo_fix within another function but I couldn't get it to work.
Is there a clean way to do this?
This should work:
#pytest.fixture
def foo_fix():
def wrapper(parameter, config):
patch(Foo.config, "hello"):
return Foo(parameter=parameter)
yield wrapper
def test_foo_1(foo_fix):
foo = foo_fix(parameter=1, config="xyz")
assert foo.go() == "abc"

Python How to create method of class in runtime

I am curious how to create a custom method for a class at runtime...
I mean for example with name of method, name of parameters, body of method read from database and assign this method to a class or to an instance.
I have a found possibility to add method that is already written:
class A:
def __init__(self):
pass
def method(self):
return True
A.method = method
a = A()
print(a.method())
but I am interested in completely assembling a new method from scratch:
name = "method"
params = ["self"] # Params in list should be a strings
body = "return True"
# To create method from pieces
Is it possible using __dict__ ? Or how else this be done?
Methods are another attribute on the object that is the class. They can be added like other attributes:
Code:
class A:
def __init__(self):
pass
def method(self):
return True
def another_method(self):
return False
setattr(A, 'another_method', another_method)
Test Code:
a = A()
print(a.another_method())
Results:
False
Methods from a string:
Add if you really need to get your methods from a database or such you can use exec like:
method_string = """
def yet_another_method(self):
return type(self).__name__
"""
exec(method_string)
setattr(A, 'yet_another_method', yet_another_method)
a = A()
print(a.yet_another_method())
Results:
A
This answer has to be treated with care, using exec or eval can run arbitary code and may compromise your system. So if you rely on user-input to create the function you mustn't use this!!!
The warning aside you can simply create anything using exec:
exec("""
def method():
return True
""")
>>> method()
True
So what you basically need is just a way to get your requirements in there:
functionname = 'funfunc'
parameters = ['a', 'b']
body = 'return a + b'
exec("""
def {functionname}({parameters}):
{body}
""".format(
functionname=functionname,
parameters=', '.join(parameters),
body='\n'.join([' {line}'.format(line=line) for line in body.split('\n')])))
The body will be indented so that it's valid syntax and the parameter list will be joined using ,. And the test:
>>> funfunc(1, 2)
3
One of the best solutions that I have found is the following:
def import_code(code, name, add_to_sys_modules=0):
"""
Import dynamically generated code as a module. code is the
object containing the code (a string, a file handle or an
actual compiled code object, same types as accepted by an
exec statement). The name is the name to give to the module,
and the final argument says wheter to add it to sys.modules
or not. If it is added, a subsequent import statement using
name will return this module. If it is not added to sys.modules
import will try to load it in the normal fashion.
import foo
is equivalent to
foofile = open("/path/to/foo.py")
foo = importCode(foofile,"foo",1)
Returns a newly generated module.
"""
import sys,imp
module = imp.new_module(name)
exec(code,module.__dict__)
if add_to_sys_modules:
sys.modules[name] = module
return module
class A:
def __init__(self):
pass
name = "method"
params = ["self"] # Params in list should be a strings
body = "return True"
scratch = "def {0}({1}):\n\t{2}".format(name, ','.join(params), body)
new_module = import_code(scratch, "test")
A.method = new_module.method
a = A()
print(a.method())
Original function import_code by the following link http://code.activestate.com/recipes/82234-importing-a-dynamically-generated-module/
Using this solution I can dynamically create methods, load them in runtime and link to whatever I want object !!

What is the right way to test callback invocation using Python unittest?

I have an application code that looks like the following.
# Filename: app.py
class Foo:
def __init__(self):
self.callback = None
def set_handler(self, callback):
self.callback = callback
def run(self, details):
name, age = details.split('/')
if age.isdigit():
age = int(age)
else:
age = -1
return self.callback(name, age)
As you can see, it offers a set_handler method to set a callback. The callback must later be invoked with two arguments: a string and an integer. I am trying to ensure this in a unittest.
# Filename: test_app.py
import unittest
from app import Foo
class AppTest(unittest.TestCase):
def f(self, a, b):
# This callback should get called with the first argument as
# string and the second argument as integer
return repr(a) + ',' + repr(b)
def test_callback(self):
foo = Foo()
foo.set_handler(self.f)
self.assertEqual(foo.run('John/20'), "'John',20")
self.assertEqual(foo.run('John/xyz'), "'John',-1")
if __name__ == '__main__':
unittest.main()
This unit test succeeds. But I don't think my way of testing is robust. This unit test is basically a hack because I don't know how to correctly test if a callback has been invoked with the right type of arguments. What I find weird about it is that AppTest's f method is sharing the responsibility of type checking by attempting to return a value which are composed of repr() of the arguments, and this is not at all robust to say the least.
Could you please help me? Is it possible to relieve the f method of the responsibility of testing the types?
EDIT:
Try using unittest.mock (standard library on Python 3.3). It allows you to assert how methods were called. For example:
import unittest
from unittest.mock import Mock
from app import Foo
class AppTest(unittest.TestCase):
def test_callback(self):
foo = Foo()
f = Mock()
foo.set_handler(f)
foo.run('John/20')
f.assert_called_with('John', 20)
foo.run('John/xyz')
f.assert_called_with('John', -1)
if __name__ == '__main__':
unittest.main()

Categories