pytest for function as an argument - python

I was writing a test using pytest library where I need to test a method which takes another method as an argument.
class Certificate:
def upload(self, upload_fn: Callable):
try:
if self.file_name:
upload_fn(self.file_name)
return
raise ValueError("File name doesn't exist")
except Exception as e:
raise e
Now I created a dummy mock function which I am passing while calling upload method but I am not sure how do I make sure if the upload_fn is called.
I am trying to achieve something like this
def test_certificate_upload(certificate):
certificate.upload(some_mock_fn)
assert some_mock_fn.called_once() == True
EDIT: so currently I am testing it in the following way but I think there can be a better approach.
def mock_upload(f_name):
""just an empty mock method""
def mock_upload_raise_error(f_name):
raise Exception e
def test_certificate_upload_raise_exception(certificate):
with pytest.raises(Exception) as e:
certificate.generate(mock_generator_raise_error)
PS: limitation to this approach is we can't assert if the method was called or how many times the method was called or with what params the method was called.
Also, we have to create extra dummy mock methods for differnet scenarios.

You an mock :
def mock_get(self, *args):
return "Result I want"
#mock.patch(upload, side_effect=mock_get)
def test_certificate_upload(certificate):
certificate.upload(some_mock_fn)
assert function_name() == Return_data

Related

Decorator which skips `self` for methods

Let's say we have multiple functions which all accept an URL as their first argument and this URL needs to be validated. This can be nicely solved with a decorator
def validate_url(f):
def validated(url, *args, **kwargs):
assert len(url.split('.')) == 3 # trivial example
return f(url, *args, **kwargs)
return validated
#validate_url
def some_func(url, some_other_arg, *some_args, **some_kwargs):
pass
This approach will work and allow me to factor the validation behavior out of many instances of similar functions. But now I would want to write a class method which also takes a validated URL. However, the naive approach will not work
class SomeClass:
#validate_url
def some_method(self, url, some_other_args):
pass
because we will end up attempting to validate self and not url. My question is how to write a single decorator which will work for both functions and methods with the minimum amount of boilerplate.
Note 1: I am aware why this happens, it's just that I don't know how to work around this in the most elegant manner.
Note 2: The URL validation problem is just an example, so checking if isinstance(args[0], str) is not a good solution.
One solution would be to somehow detect whether the decorated function is a class method or not — which seems to be difficult if not impossible (as far as I can tell anyway) to do so cleanly. The inspect module's ismethod() and isfunction() don't work inside a decorator used inside a class definition.
Given that, here's a somewhat hacky way of doing it which checks to see if the decorated callable's first argument has been given the name "self", which is the coding convention for it in class methods (although it is not a requirement, so caveat emptor and use at your own risk).
The following code seems to work in both Python 2 and 3. However in Python 3 it may raise DeprecationWarnings depending on exactly what sub-version is being used—so they have been suppressed in a section of the code below.
from functools import wraps
import inspect
import warnings
def validate_url(f):
#wraps(f)
def validated(*args, **kwargs):
with warnings.catch_warnings():
# Suppress DeprecationWarnings in this section.
warnings.simplefilter('ignore', category=DeprecationWarning)
# If "f"'s first argument is named "self",
# assume it's a method.
if inspect.getargspec(f).args[0] == 'self':
url = args[1]
else: # Otherwise assume "f" is a ordinary function.
url = args[0]
print('testing url: {!r}'.format(url))
assert len(url.split('.')) == 3 # Trivial "validation".
return f(*args, **kwargs)
return validated
#validate_url
def some_func(url, some_other_arg, *some_args, **some_kwargs):
print('some_func() called')
class SomeClass:
#validate_url
def some_method(self, url, some_other_args):
print('some_method() called')
if __name__ == '__main__':
print('** Testing decorated function **')
some_func('xxx.yyy.zzz', 'another arg')
print(' URL OK')
try:
some_func('https://bogus_url.com', 'another thing')
except AssertionError:
print(' INVALID URL!')
print('\n** Testing decorated method **')
instance = SomeClass()
instance.some_method('aaa.bbb.ccc', 'something else') # -> AssertionError
print(' URL OK')
try:
instance.some_method('foo.bar', 'arg 2') # -> AssertionError
except AssertionError:
print(' INVALID URL!')
Output:
** Testing decorated function **
testing url: 'xxx.yyy.zzz'
some_func() called
URL OK
testing url: 'https://bogus_url.com'
INVALID URL!
** Testing decorated method **
testing url: 'aaa.bbb.ccc'
some_method() called
URL OK
testing url: 'foo.bar'
INVALID URL!

Python mock a cached method

I have a class that caches a method:
class Foo:
def __init__(self, package: str):
self.is_installed = functools.lru_cache()(
self.is_installed)
def is_installed():
#implementation here
And code that calls the method by looping on the instances of the class
try:
if Foo('package').is_installed():
except Exception as e:
print('Could not install')
else:
print('Installed properly')
I am trying to test this code by mocking the is_installed method to throw an exception.
#patch.object(Foo, 'is_installed')
def test_exception_installing_bear(self, mock_method):
mock_method.side_effect = Exception('Something bad')
# code to assert 'could not install' in stdout
But it does not work. The exception is not thrown and the assertion fails. On the other hand the output shows that it installed properly. I think it has something to be cached. What am I doing wrong?
see document
unittest.TestCase.assertRaises
Alternative
with self.assertRaises(Exception):
mock_args = {'side_effect': Exception}
with mock.patch('foo.Foo.is_installed', **mock_args):
Foo.is_installed

Python: decorator/wrapper for try/except statement

I have some blocks of code which need to be wrapped by function.
try:
if config.DEVELOPMENT == True:
# do_some_stuff
except:
logger.info("Config is not set for development")
Then I'll do again:
try:
if config.DEVELOPMENT == True:
# do_some_another_stuff
except:
logger.info("Config is not set for development")
So, how can I wrap this "do_some_stuff" and "do_some_another_stuff"?
I'm trying to write function with contextmanager:
#contextmanager
def try_dev_config(name):
try:
if name is not None:
yield
except Exception as e:
print "not dev config"
with try_dev_config("config.DEVELOPMENT"):
# do_some_stuff
And I got an error:
RuntimeError: generator didn't yield
You could pass in a function.
boolean = True
def pass_this_in():
print("I just did some stuff")
def the_try_except_bit(function):
try:
if boolean:
function()
except:
print("Excepted")
# Calling the above code
the_try_except_bit(pass_this_in)
If you want to reduce the "pass_this_in" definition bit, then you can use lambda function definitions:
pass_this_in = lambda : print("I just did some stuff")
I am not sure that a context manager is the good method to achieve what you want. The context manager goal is to provide a mecanism to open/instantiate a resource, give access to it (or not) and close/clean it automatically when you no more need it.
IMHO, what you need is a decorator.
A decorator aims at executing code around a function call. It would force you to put each block of code in a function but I don't think it is so difficult. You can implement it like this:
class Config(object):
"""for demonstration purpose only: used to have a config.DEVELOPMENT value"""
DEVELOPMENT = True
class Logger(object):
"""for demonstration purpose only: used to have a logger.info method"""
#staticmethod
def info(msg):
print("Logged: {}".format(msg))
def check_dev_config(config, logger):
def dev_config_checker(func):
def wrapper(*args, **kwargs):
try:
if config.DEVELOPMENT:
func(*args, **kwargs)
except Exception as err:
logger.info(
"Config is not set for developpement: {}".format(err))
return wrapper
return dev_config_checker
#check_dev_config(Config, Logger)
def do_stuff_1():
print("stuff 1 done")
#check_dev_config(Config, Logger)
def do_stuff_2():
raise Exception("stuff 2 failed")
do_stuff_1()
do_stuff_2()
This code prints
stuff 1 done
Logged: Config is not set for developpement: stuff 2 failed
Explanations:
The check_dev_config function is actually a decorator generator which accepts the config and the logger as arguments.
It returns the dev_config_checker function which is an actual (and parameterised) decorator, and which accepts a function to decorate as argument.
This decorator returns a wrapper function which will actually run code around the decorated function call. In this function, the decorated function is called inside a try/except structure and only if the config.DEVELOPMENT is evaluated to True. In case of exception, the logger is used to log an information.
Each block of code to decorate is put into a function (do_stuff_1, do_stuff_2 and decorated with the check_dev_config decorator generator, giving it the config and the logger.
When decorated functions are called, they are called via their decorator and not directly. As you can see, the do_stuff_2 exception has been catched and the a message has been logged.

Better way to use try except block

I have a requirement to execute multiple Python statements and few of them might fail during execution, even after failing I want the rest of them to be executed.
Currently, I am doing:
try:
wx.StaticBox.Destroy()
wx.CheckBox.Disable()
wx.RadioButton.Enable()
except:
pass
If any one of the statements fails, except will get executed and program exits. But what I need is even though it is failed it should run all three statements.
How can I do this in Python?
Use a for loop over the methods you wish to call, eg:
for f in (wx.StaticBox.Destroy, wx.CheckBox.Disable, wx.RadioButton.Enable):
try:
f()
except Exception:
pass
Note that we're using except Exception here - that's generally much more likely what you want than a bare except.
If an exception occurs during a try block, the rest of the block is skipped. You should use three separate try clauses for your three separate statements.
Added in response to comment:
Since you apparently want to handle many statements, you could use a wrapper method to check for exceptions:
def mytry(functionname):
try:
functionname()
except Exception:
pass
Then call the method with the name of your function as input:
mytry(wx.StaticBox.Destroy)
I would recommend creating a context manager class that suppress any exception and the exceptions to be logged.
Please look at the code below. Would encourage any improvement to it.
import sys
class catch_exception:
def __init__(self, raising=True):
self.raising = raising
def __enter__(self):
pass
def __exit__(self, type, value, traceback):
if issubclass(type, Exception):
self.raising = False
print ("Type: ", type, " Log me to error log file")
return not self.raising
def staticBox_destroy():
print("staticBox_destroy")
raise TypeError("Passing through")
def checkbox_disable():
print("checkbox_disable")
raise ValueError("Passing through")
def radioButton_enable():
print("radioButton_enable")
raise ValueError("Passing through")
if __name__ == "__main__":
with catch_exception() as cm:
staticBox_destroy()
with catch_exception() as cm:
checkbox_disable()
with catch_exception() as cm:
radioButton_enable()

how to mock an exception call in python?

I have a code like this:
def extract(data):
if len(data) == 3:
a = 3
else:
component = data.split("-")
if len(component) == 3:
a,b,c = component
else:
raise globals.myException("data1", "Incorrect format", data)
return a,b,c
This is a simplified one. I want to mock the exception class globals.myException. I'm doing that:
def test_extract_data_throws_exception(self):
with patch('globals.myException') as mock:
mock.__init__("data1", "Incorrect format", "")
with self.assertRaises(myException):
self.assertEqual(extract(""), (""))
And I always get the error: "TypeError: exceptions must be old-style classes or derived from BaseException, not MagicMock"
EDIT: As #Aaron Digulla suggest, monkey patching is the correct solution. I post the solution to help others.
def test_extract_data_throws_exception(self):
#monkey patching
class ReplaceClass(myException):
def __init__(self, module, message, detail = u''):
pass
globals.myException = ReplaceClass
with self.assertRaises(myException:
self.assertEqual(extract(""), (""))
The reason is raise checks the type of the argument. It must be a string (a.k.a "old style exceptions") or derived from BaseException
Since a mock isn't either, raise refuses to use it.
In this specific case, you either have to raise the exception or use monkey patching (= overwrite the symbol globals.myException in your test and restore it afterwards).

Categories