Reputation: 1501
I've written a small command line application using Click and Python, which I am now trying to write tests for (mainly for the purpose of learning how to test Click apps so I can move on to testing the one I'm actually developing).
Here's a function I'm trying to test:
@click.group()
def main():
pass
@main.command()
@click.option('--folder', '-f', prompt="What do you want to name the folder? (No spaces please)")
def create_folder(folder):
while True:
if " " in folder:
click.echo("Please enter a name with no spaces.")
folder = click.prompt("What do you want to name the folder?", type=str)
if folder in os.listdir():
click.echo("This folder already exists.")
folder = click.prompt("Please choose a different name for the folder")
else:
break
os.mkdir(folder)
click.echo("Your folder has been created!")
I'm trying to test this using the built in testing from Click (http://click.pocoo.org/6/testing/ and http://click.pocoo.org/6/api/#testing for more details) as well as pytest. This works well in the case where I test an acceptable folder name (ie. one that does not have spaces and that does not already exist). See below:
import clicky # the module we're testing
import os
from click.testing import CliRunner
import click
import pytest
input sys
runner = CliRunner()
folder = "myfolder"
folder_not = "my folder"
question_create = "What do you want to name the folder? (No spaces please): "
echoed = "\nYour folder has been created!\n"
def test_create_folder():
with runner.isolated_filesystem():
result = runner.invoke(clicky.create_folder, input=folder)
assert folder in os.listdir()
assert result.output == question_create + folder + echoed
I now want to test this function in the case where I provide a folder name that's not allowed, such as one with spaces, and then after the prompt telling me I can't have spaces, I will provide an acceptable folder name. However, I cannot figure out how to make click.runner
accept more than one input value, and that's the only way I can think to make that work. I'm also open to using unittest mocking, but I'm not sure how to integrate that into the way Click does tests, which other than this problem has worked wonderfully so far. Here's my attempt with multiple inputs:
def test_create_folder_not():
with runner.isolated_filesystem():
result = runner.invoke(clicky.create_folder, input=[folder_not, folder]) # here i try to provide more than one input
assert result.output == question_create + folder_not + "\nPlease enter a name with no spaces.\n" + "What do you want to name the folder?: " + folder + echoed
I tried to provide multiple inputs by putting them in a list, like what I've seen done with mocking, but I get this error:
'list' object has no attribute 'encode'
Any thoughts on this would be greatly appreciated!
Upvotes: 5
Views: 2714
Reputation: 49794
To provide more than one input to the test runner, you can simply join()
the inputs together with a \n
like so:
result = runner.invoke(clicky.create_folder,
input='\n'.join([folder_not, folder]))
============================= test session starts =============================
platform win32 -- Python 3.6.3, pytest-3.3.2, py-1.5.2, pluggy-0.6.0
rootdir: \src\testcode, inifile:
collected 2 items
testit.py .. [100%]
========================== 2 passed in 0.53 seconds ===========================
click.ParamType
I however would suggest that for prompting of the desired filename properties, you can use ParamType
. Click offers a ParamType
that can be subclassed, and then passed to click.option()
. The format checking and re-prompting can then be taken care of by click.
You can subclass a ParamType like:
import click
class NoSpacesFolder(click.types.StringParamType):
def convert(self, value, param, ctx):
folder = super(NoSpacesFolder, self).convert(value, param, ctx)
if ' ' in folder:
raise self.fail("No spaces allowed in selection '%s'." %
value, param, ctx)
if folder in os.listdir():
raise self.fail("This folder already exists.\n"
"Please choose another name.", param, ctx)
return folder
To use the custom ParamType, pass it to click.option()
like:
@main.command()
@click.option(
'--folder', '-f', type=NoSpacesFolder(),
prompt="What do you want to name the folder? (No spaces please)")
def create_folder(folder):
os.mkdir(folder)
click.echo("Your folder has been created!")
Upvotes: 3