I am trying to run a test case for a python script. Test case is successful when I don't use Click decorator and argument in python script method.
But when I am using it, it gives SystemExit: 0
.
commands.py:
import sys
import click
@click.command()
@click.argument('bar_info', nargs=-1)
def get_all_foos(bar_info):
print("bars_to_retain --> {}".format(bar_info[1]))
print("bars_prefix --> {}".format(bar_info[0]))
# do something with bar_info
return "result"
tests/test_commands.py:
import sys
import pytest
import commands
from click.testing import CliRunner
def test_foo_list():
response = commands.get_all_foos(["ctm", "10"])
print("response is --> {}".format(response))
When I run the test case:
pytest -rA tests/test_commands.py
then the test fails with:
FAILED tests/test_commands.py::test_foo_list - SystemExit: 0
The full output is:
============================= test session starts ==============================
platform darwin -- Python 3.10.3, pytest-7.1.2, pluggy-1.0.0
rootdir: ...
collected 1 item
tests/test_commands.py F [100%]
=================================== FAILURES ===================================
________________________________ test_foo_list _________________________________
self = <Command get-all-foos>, args = [], prog_name = 'pytest'
complete_var = None, standalone_mode = True, windows_expand_args = True
extra = {}, ctx = <click.core.Context object at 0x107585d20>, rv = 'result'
def main(
self,
args: t.Optional[t.Sequence[str]] = None,
prog_name: t.Optional[str] = None,
complete_var: t.Optional[str] = None,
standalone_mode: bool = True,
windows_expand_args: bool = True,
**extra: t.Any,
) -> t.Any:
"""This is the way to invoke a script with all the bells and
whistles as a command line application. This will always terminate
the application after a call. If this is not wanted, ``SystemExit``
needs to be caught.
This method is also available by directly calling the instance of
a :class:`Command`.
:param args: the arguments that should be used for parsing. If not
provided, ``sys.argv[1:]`` is used.
:param prog_name: the program name that should be used. By default
the program name is constructed by taking the file
name from ``sys.argv[0]``.
:param complete_var: the environment variable that controls the
bash completion support. The default is
``"_<prog_name>_COMPLETE"`` with prog_name in
uppercase.
:param standalone_mode: the default behavior is to invoke the script
in standalone mode. Click will then
handle exceptions and convert them into
error messages and the function will never
return but shut down the interpreter. If
this is set to `False` they will be
propagated to the caller and the return
value of this function is the return value
of :meth:`invoke`.
:param windows_expand_args: Expand glob patterns, user dir, and
env vars in command line args on Windows.
:param extra: extra keyword arguments are forwarded to the context
constructor. See :class:`Context` for more information.
.. versionchanged:: 8.0.1
Added the ``windows_expand_args`` parameter to allow
disabling command line arg expansion on Windows.
.. versionchanged:: 8.0
When taking arguments from ``sys.argv`` on Windows, glob
patterns, user dir, and env vars are expanded.
.. versionchanged:: 3.0
Added the ``standalone_mode`` parameter.
"""
if args is None:
args = sys.argv[1:]
if os.name == "nt" and windows_expand_args:
args = _expand_args(args)
else:
args = list(args)
if prog_name is None:
prog_name = _detect_program_name()
# Process shell completion requests and exit early.
self._main_shell_completion(extra, prog_name, complete_var)
try:
try:
with self.make_context(prog_name, args, **extra) as ctx:
rv = self.invoke(ctx)
if not standalone_mode:
return rv
# it's not safe to `ctx.exit(rv)` here!
# note that `rv` may actually contain data like "1" which
# has obvious effects
# more subtle case: `rv=[None, None]` can come out of
# chained commands which all returned `None` -- so it's not
# even always obvious that `rv` indicates success/failure
# by its truthiness/falsiness
> ctx.exit()
lib/python3.10/site-packages/click/core.py:1065:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <click.core.Context object at 0x107585d20>, code = 0
def exit(self, code: int = 0) -> "te.NoReturn":
"""Exits the application with a given exit code."""
> raise Exit(code)
E click.exceptions.Exit: 0
lib/python3.10/site-packages/click/core.py:687: Exit
During handling of the above exception, another exception occurred:
def test_foo_list():
> response = commands.get_all_foos(["ctm", "10"])
tests/test_commands.py:9:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
lib/python3.10/site-packages/click/core.py:1130: in __call__
return self.main(*args, **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Command get-all-foos>, args = [], prog_name = 'pytest'
complete_var = None, standalone_mode = True, windows_expand_args = True
extra = {}, ctx = <click.core.Context object at 0x107585d20>, rv = 'result'
def main(
self,
args: t.Optional[t.Sequence[str]] = None,
prog_name: t.Optional[str] = None,
complete_var: t.Optional[str] = None,
standalone_mode: bool = True,
windows_expand_args: bool = True,
**extra: t.Any,
) -> t.Any:
"""This is the way to invoke a script with all the bells and
whistles as a command line application. This will always terminate
the application after a call. If this is not wanted, ``SystemExit``
needs to be caught.
This method is also available by directly calling the instance of
a :class:`Command`.
:param args: the arguments that should be used for parsing. If not
provided, ``sys.argv[1:]`` is used.
:param prog_name: the program name that should be used. By default
the program name is constructed by taking the file
name from ``sys.argv[0]``.
:param complete_var: the environment variable that controls the
bash completion support. The default is
``"_<prog_name>_COMPLETE"`` with prog_name in
uppercase.
:param standalone_mode: the default behavior is to invoke the script
in standalone mode. Click will then
handle exceptions and convert them into
error messages and the function will never
return but shut down the interpreter. If
this is set to `False` they will be
propagated to the caller and the return
value of this function is the return value
of :meth:`invoke`.
:param windows_expand_args: Expand glob patterns, user dir, and
env vars in command line args on Windows.
:param extra: extra keyword arguments are forwarded to the context
constructor. See :class:`Context` for more information.
.. versionchanged:: 8.0.1
Added the ``windows_expand_args`` parameter to allow
disabling command line arg expansion on Windows.
.. versionchanged:: 8.0
When taking arguments from ``sys.argv`` on Windows, glob
patterns, user dir, and env vars are expanded.
.. versionchanged:: 3.0
Added the ``standalone_mode`` parameter.
"""
if args is None:
args = sys.argv[1:]
if os.name == "nt" and windows_expand_args:
args = _expand_args(args)
else:
args = list(args)
if prog_name is None:
prog_name = _detect_program_name()
# Process shell completion requests and exit early.
self._main_shell_completion(extra, prog_name, complete_var)
try:
try:
with self.make_context(prog_name, args, **extra) as ctx:
rv = self.invoke(ctx)
if not standalone_mode:
return rv
# it's not safe to `ctx.exit(rv)` here!
# note that `rv` may actually contain data like "1" which
# has obvious effects
# more subtle case: `rv=[None, None]` can come out of
# chained commands which all returned `None` -- so it's not
# even always obvious that `rv` indicates success/failure
# by its truthiness/falsiness
ctx.exit()
except (EOFError, KeyboardInterrupt):
echo(file=sys.stderr)
raise Abort() from None
except ClickException as e:
if not standalone_mode:
raise
e.show()
sys.exit(e.exit_code)
except OSError as e:
if e.errno == errno.EPIPE:
sys.stdout = t.cast(t.TextIO, PacifyFlushWrapper(sys.stdout))
sys.stderr = t.cast(t.TextIO, PacifyFlushWrapper(sys.stderr))
sys.exit(1)
else:
raise
except Exit as e:
if standalone_mode:
> sys.exit(e.exit_code)
E SystemExit: 0
lib/python3.10/site-packages/click/core.py:1083: SystemExit
----------------------------- Captured stdout call -----------------------------
bars_to_retain --> 10
bars_prefix --> ctm
=========================== short test summary info ============================
FAILED tests/test_commands.py::test_foo_list - SystemExit: 0
============================== 1 failed in 0.15s ===============================
Don't run click
commands directly; they will trigger sys.exit(0)
when done, which is the normal and correct way to end a command-line tool, but not very useful when trying to test the command.
Instead, follow the Testing Click Applications chapter and use CliRunner()
object to run your commands:
from click.testing import CliRunner
# ...
def test_foo_list():
runner = CliRunner()
result = runner.invoke(commands.get_all_foos, ["ctm", "10"])
response = result.return_value
Note that I passed in a list of string arguments; click
will parse those into the correct structure.
I assigned result.return_value
to response
here, but know that that'll be None
; click commands are not supposed to return anything really, because they normally would communicate with the user via the terminal or by interacting with the filesystem, etc.:
________________________________ test_foo_list _________________________________
----------------------------- Captured stdout call -----------------------------
response is --> None
=========================== short test summary info ============================
PASSED tests/test_commands.py::test_foo_list
You could perhaps use print()
or click.echo()
to write something to stdout
and then test for that output via result.stdout
, though.