Capturing print from exception mocked raised with side_effect - python-3.x

How can we capture the print statement within an except when the exception is mocked?
In the example below, I am mocking the make_request_and_get_response using side_effect.
Then, using the pytest.raises I assert that this exception was raised. But, when trying to assert that the corresponding print statement is being output by using capsys to capture it, nothing is captured.
Am I correct to expect that since I am mocking the exception the corresponding print statement will be executed?
import pytest
from unittest import mock
from foo.main import get_response_from_external_api
def test_url_error(capsys):
mock_query = mock.Mock()
mock_query.make_request_and_get_response.side_effect = Exception('HTTPError')
with pytest.raises(Exception) as excinfo:
get_response_from_external_api(mock_query)
assert str(excinfo.value) == 'HTTPError '
out, err = capsys.readouterr()
assert "Got a HTTPError" in out # AssertionError
query.py
from foo.query import *
def get_response_from_external_api(query):
response = 0
try:
response = query.make_request_and_get_response()
except urllib.error.HTTPError as e:
print('Got a HTTPError: ', e)
return response
if __name__ == "__main__":
query = Query('input A', 'input B')
result = get_response_from_external_api(query)
return result
main.py
from foo.query import *
def get_response_from_external_api(query):
response = 0
try:
response = query.make_request_and_get_response()
except urllib.error.HTTPError as e:
print('Got a HTTPError: ', e)
return response
if __name__ == "__main__":
query = Query('input A', 'input B')
result = get_response_from_external_api(query)
return resul

Related

In pytest, how to test for sys.exit('some error message')?

I'm a beginner to python and pytest. I have a function that I'm trying to test for the exceptions part with Pytest:
def read_data(args) -> tuple[Polyline, str]:
parsed_args = input_parsing_1(args)
try:
with open(parsed_args.f) as f_input:
reader = csv.reader(f_input)
polyline = fill_poly_with_data(reader)
except FileNotFoundError as e:
sys.exit('Invalid input file \n')
else:
return (polyline, parsed_args.f)
I want to test if exceptions are risen and if the error message matches the one I put in the code above.
My attempts
#patch('project.open')
def test_read_data_error_SE(mock_open):
mock_open.side_effect = SystemExit
with pytest.raises(SystemExit):
assert read_data(['-f', ''])
#patch('project.open')
def test_read_data_error_FNFE2(mock_open):
mock_open.side_effect = FileNotFoundError
with pytest.raises(SystemExit):
with pytest.raises(FileNotFoundError):
assert read_data(['-f', 'cos'])
The above tests works fine.
I also wish to assert if sys.exit message matches 'Invalid input file \n'. I've tried:
#patch('project.open')
def test_read_data_error_SE1(mock_open):
mock_open.side_effect = SystemExit
with pytest.raises(SystemExit, match='Invalid input file'):
assert read_data(['-f', ''])
#patch('project.open')
def test_read_data_error_SE2(mock_open):
mock_open.side_effect = SystemExit
with pytest.raises(SystemExit) as e:
assert read_data(['-f', ''])
assert 'Invalid input file' in str(e.value)
but those tests fails:
===================================================== short test summary info ======================================================
FAILED test_project.py::test_read_data_error_SE1 - AssertionError: Regex pattern did not match.
FAILED test_project.py::test_read_data_error_SE2 - AssertionError: assert 'Invalid input file' in ''
I have seen some posts here on stackoverflow, like:
Verify the error code or message from SystemExit in pytest
How to properly assert that an exception gets raised in pytest?
Catch SystemExit message with Pytest
but none of them seems to answer my problem.
My questions:
It seems like my tested message 'Invalid input file' is being matched to an empty string ''? Why? How to properly catch and assert the sys.exit('some error message')?
You're seeing an empty string because you're raising SystemExit with no parameters:
#patch('project.open')
def test_read_data_error_SE1(mock_open):
mock_open.side_effect = SystemExit
with pytest.raises(SystemExit, match='Invalid input file'):
assert read_data(['-f', ''])
You've set mock_open.side_effect = SystemExit, so when you're code calls open(), it raises SystemExit with no parameters. If you want to see a message like Invalid input file, have your code explicitly exit by calling sys.exit('Invalid input file').
Your code already does that, but you're pre-empting that behavior by having the call to open raise SystemExit instead. You probably want open to raise FileNotFound instead:
#patch('project.open')
def test_read_data_error_SE1(mock_open):
mock_open.side_effect = FileNotFound
with pytest.raises(SystemExit, match='Invalid input file'):
assert read_data(['-f', ''])
This will get caught by the except in read_data, which will then call sys.exit.
Here's a complete example (I've included some stub functions here so that I can use your read_data method largely unmodified):
import csv
import sys
import types
import pytest
from unittest import mock
def fill_poly_with_data(x):
return x
def input_parsing_1(args):
return types.SimpleNamespace(f="testing")
def read_data(args) -> tuple:
parsed_args = input_parsing_1(args)
try:
with open(parsed_args.f) as f_input:
reader = csv.reader(f_input)
polyline = fill_poly_with_data(reader)
except FileNotFoundError:
sys.exit('Invalid input file \n')
else:
return (polyline, parsed_args.f)
#mock.patch('builtins.open')
def test_function_that_exits(mock_open):
mock_open.side_effect = FileNotFoundError
with pytest.raises(SystemExit, match="Invalid input file"):
read_data(['-f', 'cos'])

Pytest - patched function returns mock instead of value

I've the following very simply testcase, which for some unknown reason returns mock (for do2() ) instead of a json:
def return_json():
return json.dumps({'a': 5})
def test_700():
with patch('app.main.Pack') as mocked_pack:
mocked_pack.do2 = return_json() #####
with app.test_client() as c:
resp = c.get('/popular/')
assert resp.status_code == 200
I tried to use return_value and similar things, but I always get the traceback: the JSON object must be str, bytes or bytearray, not MagicMock . I think the problem should be how I patch it, but I can't find out.
Could anyone point out how can I return a real value for do2(), not only a mock?
My setup is as this:
#main.py
from persen import Pack
def popular_view():
my_pack = Pack(name='peter', age='25')
response = my_pack.do2()
try:
json.loads(response)
except Exception as e:
print('E1 = ', e)
#persen.py
class Pack:
def __init__(self, name, age):
...
def do1(self):
...
def do2(self):
return '600a'

Mock exception with side effect raised in class method and caught in calling method gives 'did not raise'

Using side_effect, I am trying to raise an exception when a mock is called but I get a DID NOT RAISE EXCEPTION error that I do not understand.
Based largely on this answer, I have created a simple example where there is a Query class with a class method make_request_and_get_response which can raise several exceptions. These exceptions are being handled within the get_response_from_external_api method in main.py.
query.py
from urllib.request import urlopen
import contextlib
import urllib
class Query:
def __init__(self, a, b):
self.a = a
self.b = b
self.query = self.make_query()
def make_query(self):
# create query request using self.a and self.b
return query
def make_request_and_get_response(self): # <--- the 'dangerous' method that can raise exceptions
with contextlib.closing(urlopen(self.query)) as response:
return response.read().decode('utf-8')
main.py
from foo.query import *
def get_response_from_external_api(query):
try:
response = query.make_request_and_get_response()
except urllib.error.URLError as e:
print('Got a URLError: ', e)
except urllib.error.HTTPError as e:
print('Got a HTTPError: ', e)
# {{various other exceptions}}
except Exception:
print('Got a generic Exception!')
# handle this exception
if __name__ == "__main__":
query = Query('input A', 'input B')
result = get_response_from_external_api(query)
return result
Using pytest, I am trying to mock that 'dangerous' method (make_request_and_get_response) with a side effect for a specific exception. Then, I proceed with creating a mocked Query object to use when calling the make_request_and_get_response with the expectation that this last call give a 'URLError' exception.
test_main.py
import pytest
from unittest.mock import patch
from foo.query import Query
from foo.main import get_response_from_external_api
class TestExternalApiCall:
#patch('foo.query.Query')
def test_url_error(self, mockedQuery):
with patch('foo.query.Query.make_request_and_get_response', side_effect=Exception('URLError')):
with pytest.raises(Exception) as excinfo:
q= mockedQuery()
foo.main.get_response_from_external_api(q)
assert excinfo.value = 'URLError'
# assert excinfo.value.message == 'URLError' # this gives object has no attribute 'message'
The test above gives the following error:
> foo.main.get_response_from_external_api(q)
E Failed: DID NOT RAISE <class 'Exception'> id='72517784'>") == 'URLError'
Pytest cannot detect an exception because in get_response_from_external_api you are catching all of them. Depending on your requirements, you have these options:
Don't catch the exceptions in get_response_from_external_api.
Catch the exceptions, do whatever you want and then re-raise them.
Instead of detecting an exception with pytest.raises, use capsys fixture to capture what is being printed and make assertions on that output.

How to handle exception with imap_unordered in python multiprocessing

I am using pool.imap_unordered to apply a function over different txt files saved locally.
Is it possible to capture the exception and pass?
If my code runs into an exception, it blocks the entire loop.
pool = Pool(processes=15)
results = {}
files = glob.glob('{}/10K_files/*.txt'.format(path_input))
for key, output in tqdm(pool.imap_unordered(process_file, files),total=len(files)):
results[key] = output
I've tried something like this:
pool = Pool(processes=15)
results = {}
files = glob.glob('{}/10K_files/*.txt'.format(path_input))
try:
for key, output in tqdm(pool.imap_unordered(process_file, files), total=len(files)):
results[key] = output
except:
print("error")
but then I want to resume the loop from where I started.
Thanks!
You could catch the exception in process_file and return it. Then test for whether the return value is an exception. Here is an example:
import os
import traceback
import multiprocessing as mp
def main():
work_items = [i for i in range(20)]
pool = mp.Pool()
for result in pool.imap_unordered(process_file_exc, work_items):
if isinstance(result, Exception):
print("Got exception: {}".format(result))
else:
print("Got OK result: {}".format(result))
def process_file_exc(work_item):
try:
return process_file(work_item)
except Exception as ex:
return Exception("Err on item {}".format(work_item)
+ os.linesep + traceback.format_exc())
def process_file(work_item):
if work_item == 9:
# this will raise ZeroDivisionError exception
return work_item / 0
return "{} * 2 == {}".format(work_item, work_item * 2)
if __name__ == '__main__':
main()

How can I catch an exception thrown in a function that I'm calling dynamically?

I got distracted and ended up writing a test framework in python. I'm struggling with a particular problem that this throws up.
During my assertion, I want to throw an exception as a way of bubbling a problem up to the run_test() method without requiring the user to have any knowledge of the framework. The problem is that when I do that, it seems that the try/catch block is not honoured.
Here is a cut down version of my fledgling framework:
# test_framework.py
import inspect
import module_containing_tests
class AssertionException(Exception):
def __init__(self, message):
self.message = message
def __str__(self):
return self.message
def run_test(test_name, test_method):
try:
print(">", test_name)
test_method()
print("Passed")
except AssertionException as error:
print("Failed")
print(str(error))
def assert_true(conditional):
if not conditional:
raise AssertionException("Expected True. Was False")
def test(func):
func.is_test = True
return func
members = inspect.getmembers(module_containing_tests)
for member in members:
if "is_test" in dir(member[1]) and not member[0] == "module_containing_tests":
run_test(member[0], member[1])
The module containing the tests will look like this:
# module_containing_tests.py
from test_framework import *
#test
def passing_test():
assert_true(1 + 2 == 3)
#test
def failing_test():
assert_true(1 + 2 == 5)
The output has all the exception stack tracing in it and it also halts the execution
λ python test_framework.py
> failing_test
Traceback (most recent call last):
File "test_framework.py", line 29, in <module>
run_test(member[0], member[1])
File "test_framework.py", line 13, in run_test
test_method()
File "C:\Git\simpy-test\module_containing_tests.py", line 9, in failing_test
assert_true(1 + 2 == 5)
File "C:\Git\simpy-test\test_framework.py", line 20, in assert_true
raise AssertionException("Expected True. Was False")
test_framework.AssertionException: Expected True. Was False
What I want is something like this:
λ python test_framework.py
> failing_test
Expected True. Was False
Failed
> passing_test
Passed
I think the issue is partly in the circular reference between the two files that might mess up with the visibility of the methods (as somehow explained here) and partly, maybe, in the approach. If you think about how many other testing framework work, you often have 3 elements, the unit to test, the testing framework and a test runner.
So if we try to split everythig folllowing that logic you end up having:
test_framework.py
# test_framework.py
class AssertionException(Exception):
pass
def test(f):
f.is_test = True
return f
def assert_true(conditional):
if not conditional:
raise AssertionException("Expected True. Was False")
test_runner.py
# test_runner.py
import inspect
import unit_test
from test_framework import AssertionException
def run_test(test_name, test_method):
try:
print(">", test_name)
test_method()
print("Passed")
except AssertionException as error:
print("Failed with AssertionException: " + str(error))
except Exception as error:
print("Failed with Exception: " + str(error))
if __name__ == "__main__":
members = inspect.getmembers(unit_test)
for member in members:
if "is_test" in dir(member[1]):
run_test(member[0], member[1])
unit_test.py
# unit_test.py
from test_framework import *
#test
def a_passing_test():
assert_true(1 + 2 == 3)
#test
def z_failing_test():
assert_true(1 + 2 == 5)
With this setup the circular dependency is removed and all the visibility context are respected and the output/behaviour is the expected one.
I hope it helps.
Not sure this is what you want but this works.
Copied from here Hide traceback unless a debug flag is set
Output:
$ ./test_framework.py
> a_passing_test
Passed
> z_failing_test
test_framework.AssertionException: Expected True. Was False
First file:
#!/usr/bin/env python3
#test_framework.py
import inspect
import module_containing_tests
import sys
class AssertionException(Exception):
def __init__(self, message):
self.message = message
def __str__(self):
return self.message
def run_test(test_name, test_method):
try:
print(">", test_name)
test_method()
print("Passed")
except AssertionException as error:
print("Failed")
print(str(error))
def assert_true(conditional):
if not conditional:
raise AssertionException("Expected True. Was False")
def test(func):
func.is_test = True
return func
sys.tracebacklimit=0
members = inspect.getmembers(module_containing_tests)
for member in members:
if "is_test" in dir(member[1]) and not member[0] == "module_containing_tests":
run_test(member[0], member[1])
second file:
#!/usr/bin/env python3
#module_containing_tests.py
from test_framework import *
#test
def a_passing_test():
assert_true(1 + 2 == 3)
#test
def z_failing_test():
assert_true(1 + 2 == 5)

Resources