Repository: vocalpy/vak

Issue:
add unit tests for `main` function
to avoid bugs like the one fixed in 4e82e63f3641929e240ec9373b30a79c65b714ea

Discussion:

Patch:
diff --git a/src/vak/__main__.py b/src/vak/__main__.py
index 971a7254..955768fb 100644
--- a/src/vak/__main__.py
+++ b/src/vak/__main__.py
@@ -5,12 +5,13 @@ Example: python -m vak --help
 import argparse
 from pathlib import Path
 
+from .cli import cli
+
 
 def get_parser():
     """returns ArgumentParser instance used by main()"""
-    from .cli import cli  # avoid circular import
-
     parser = argparse.ArgumentParser(
+        prog='vak',
         description="vak command-line interface",
         formatter_class=argparse.RawTextHelpFormatter,
     )
@@ -32,16 +33,18 @@ def get_parser():
     return parser
 
 
-def main():
-    """main function, called when package is run with `python -m vak` or
+def main(args=None):
+    """Main function called when run as script or through command-line interface
+
+    called when package is run with `python -m vak` or
     alternatively just calling `vak` at the command line (because this
-    function is installed under just `vak` as a console script entry point,
-    see setup.py)
-    """
-    from .cli import cli  # avoid circular import
+    function is installed under just `vak` as a console script)
 
-    parser = get_parser()
-    args = parser.parse_args()
+    ``args`` is used for unit testing only
+    """
+    if args is None:
+        parser = get_parser()
+        args = parser.parse_args()
     cli.cli(command=args.command, config_file=args.configfile)
 
 
diff --git a/src/vak/cli/cli.py b/src/vak/cli/cli.py
index 1ea78d80..f10f0b7b 100644
--- a/src/vak/cli/cli.py
+++ b/src/vak/cli/cli.py
@@ -1,8 +1,26 @@
-from .eval import eval
-from .train import train
-from .learncurve import learning_curve
-from .predict import predict
-from .prep import prep
+def eval(toml_path):
+    from .eval import eval
+    eval(toml_path=toml_path)
+
+
+def train(toml_path):
+    from .train import train
+    train(toml_path=toml_path)
+
+
+def learncurve(toml_path):
+    from .learncurve import learning_curve
+    learning_curve(toml_path=toml_path)
+
+
+def predict(toml_path):
+    from .predict import predict
+    predict(toml_path=toml_path)
+
+
+def prep(toml_path):
+    from .prep import prep
+    prep(toml_path=toml_path)
 
 
 COMMAND_FUNCTION_MAP = {
@@ -10,14 +28,15 @@ COMMAND_FUNCTION_MAP = {
     "train": train,
     "eval": eval,
     "predict": predict,
-    "learncurve": learning_curve,
+    "learncurve": learncurve,
 }
 
+
 CLI_COMMANDS = tuple(COMMAND_FUNCTION_MAP.keys())
 
 
 def cli(command, config_file):
-    """command-line interface
+    """Execute the commands of the command-line interface.
 
     Parameters
     ----------


Test patch:
diff --git a/tests/test___main__.py b/tests/test___main__.py
new file mode 100644
index 00000000..eb94797a
--- /dev/null
+++ b/tests/test___main__.py
@@ -0,0 +1,78 @@
+import pathlib
+from unittest import mock
+
+import pytest
+
+import vak
+
+
+@pytest.fixture
+def parser():
+    return vak.__main__.get_parser()
+
+
+def test_parser_usage(parser,
+                      capsys):
+    with pytest.raises(SystemExit):
+        parser.parse_args(args=[''])
+    captured = capsys.readouterr()
+    assert captured.err.startswith(
+        "usage: vak [-h] command configfile"
+    )
+
+
+def test_parser_help(parser,
+                     capsys):
+    with pytest.raises(SystemExit):
+        parser.parse_args(['-h'])
+    captured = capsys.readouterr()
+    assert captured.out.startswith(
+        "usage: vak [-h] command configfile"
+    )
+
+
+DUMMY_CONFIGFILE = './configs/config_2018-12-17.toml'
+
+
+@pytest.mark.parametrize(
+    'command, raises',
+    [
+        ('prep', False),
+        ('train', False),
+        ('learncurve', False),
+        ('eval', False),
+        ('predict', False),
+        ('not-a-valid-command', True),
+    ]
+)
+def test_parser(command,
+                raises,
+                parser,
+                capsys):
+    if raises:
+        with pytest.raises(SystemExit):
+            parser.parse_args([command, DUMMY_CONFIGFILE])
+    else:
+        args = parser.parse_args([command, DUMMY_CONFIGFILE])
+        assert args.command == command
+        assert args.configfile == pathlib.Path(DUMMY_CONFIGFILE)
+
+
+@pytest.mark.parametrize(
+    'command',
+    [
+        'prep',
+        'train',
+        'learncurve',
+        'eval',
+        'predict',
+    ]
+)
+def test_main(command,
+              parser):
+    args = parser.parse_args([command, DUMMY_CONFIGFILE])
+    mock_cli_function = mock.Mock(name=f'mock_{command}')
+    with mock.patch.dict(vak.cli.cli.COMMAND_FUNCTION_MAP,
+                         {command: mock_cli_function}) as mock_command_function_map:
+        vak.__main__.main(args)
+        mock_cli_function.assert_called()
diff --git a/tests/test_console_script.py b/tests/test_console_script.py
index d355c019..cd0ce766 100644
--- a/tests/test_console_script.py
+++ b/tests/test_console_script.py
@@ -9,7 +9,7 @@ def capture(command):
 def test_cli_just_vak():
     command = ["vak"]
     out, err, returncode = capture(command)
-    assert returncode == 2
+    assert returncode > 0
     assert out == ''
     assert err.startswith('usage: vak ')