I am running tests using pytest
on python.
In the tested code, some conditions trigger a sys.exit(non_zero_code)
, using pytest
, we want to make sure that sys.exit
is called under those conditions, but obviously we do not want the system to really exit, so we mock sys
.
The problem is that now, the rest of the code will continue to execute, which I do not want. If I call pytest.exit
, the whole process exit, when I just really want only that test case to stop at this point.
A good way to visualize is to read this simple example, the "Should not execute, we exited" line will be hit when we run the tests
import os
import sys
import pytest
import logging
from pathlib import Path
logging.basicConfig(filename=os.path.join(Path(__file__).parent.resolve(), "pytest_log"),
filemode='a',
format='%(asctime)s,%(msecs)d %(name)s %(levelname)s %(message)s',
datefmt='%H:%M:%S',
level=logging.INFO)
logger = logging.getLogger(__name__)
def function_with_exit(value):
if value == 2:
sys.exit(2)
logger.info("Should not execute, we exited")
logger.info("Value is not 2")
def side_effect():
logger.info("Sys exit was called")
@pytest.mark.parametrize("value", [1, 2, 3])
def test_with_pytest(mocker, value):
mocker.patch(__name__ + '.' + sys.__name__)
function_with_exit(value)
I need to modify the mock to:
From python docs
Since exit() ultimately “only” raises an exception, it will only exit the process when called from the main thread, and the exception is not intercepted. Cleanup actions specified by finally clauses of try statements are honored, and it is possible to intercept the exit attempt at an outer level.
A call to sys.exit()
will only create and raise a new SystemExit
exception and raise it, This is not inherited from the usual Exception
class but from the BaseException
class, So it does not get caught with any regular exception handlers, instead they get escalated to the interpreter and interpreter cleans things neatly and exists.
You can catch this in pytest and perform the needed asserts.
with pytest.raises(SystemExit) as exc:
function_with_exit()
assert exc.value.code == 2