Unittest for click module
Asked Answered
M

2

7

I wrote a simple command line utility that accepts a text file and searches for a given word in it using the click module.

sfind.py

import click
@click.command()
@click.option('--name', prompt='Word or string')
@click.option('--filename', default='file.txt', prompt='file name')
@click.option('--param', default=1, prompt="Use 1 for save line and 2 for word, default: ")
def find(name, filename, param):
    """Simple program that find  word or string at text file and put it in new"""
    try:
        with open(filename) as f, open('result.txt', 'w') as f2:
            count = 0
            for line in f:
                if name in line:
                    if param == 1:
                        f2.write(line + '\n')
                    elif param == 2:
                        f2.write(name + '\n')
                    count += 1
            print("Find: {} sample".format(count))
            return count
    except FileNotFoundError:
        print('WARNING! ' + 'File: ' + filename + ' not found')


if __name__ == '__main__':
    find()

Now I need to write a test using unittest (using unittest is required).

test_sfind.py

import unittest
import sfind

class SfindTest(unittest.TestCase):
    def test_sfind(self):
        self.assertEqual(sfind.find(), 4)


if __name__ == '__main__' :
    unittest.main()

When I run the test:

python -m unittest test_sfind.py

I get an error

click.exceptions.UsageError: Got unexpected extra argument (test_sfind.py)

How can I test this click command?

Motive answered 8/11, 2018 at 8:0 Comment(19)
You have to mock sys.argv for the test.Hyphen
Is it possible using only true unittest without the use any other libraries?Motive
I think the correct way to run it is python -m unittest test_sfind.SfindTestSewer
I tried this method but the exact same errorMotive
Ah ok...but you're not passing any argument to your find() function, whereas it takes 3...Sewer
A "true" unit test tests the unit and mocks all dependencies. Is that what you mean? And what makes the difference between the mock and the unittest package?Hyphen
@Sewer If I change to self.assertEqual(sfind.find(name='and', filename='text.txt', param=1), 4) exact error TypeError: __init__() got an unexpected keyword argument 'name'Motive
I think you shouldn't specify name='and', but just pass 'and', as yours are not keyword arguments...Sewer
@Sewer do it, but now Error: Got unexpected extra arguments (a n d) exact ((Motive
By calling self.assertEqual(sfind.find('and', 'text.txt', 1),4)?Sewer
@KlausD. I mistakenly thought that for mock you need to do an additional installation of pip install ...Motive
@Sewer yes, exactlyMotive
@lepiloff sorry, my bad, I think you shouldn't pass arguments to your function after all...Sewer
mockwas an external library, but has been adopted into standard library.Hyphen
Have you thought about adding your parameters to sys.argv?Sewer
@toti08, No, unfortunately I didnt know how this can help meMotive
Maybe this piece of documentation can help you. They do testing, but without using the unittest framework...Sewer
@Sewer Yes, I read it, but I only need to use unittest, and this is the problem of making friends with unittest and click. Unittest is not my whim, but a conditionMotive
@KlausD. May be you can write sample code using mock to test my function?Motive
B
13

You can not simply call a click command and then expect it to return. The decorators applied to make a click command considerably change the behavior of the function. Fortunately the click frameworks provides for this through the CliRunner class.

Your command can be tested via unittest with something like this:

import unittest
import sfind
from click.testing import CliRunner

class TestSfind(unittest.TestCase):

    def test_sfind(self):

        runner = CliRunner()
        result = runner.invoke(
            sfind.find, '--name url --filename good'.split(), input='2')
        self.assertEqual(0, result.exit_code)
        self.assertIn('Find: 3 sample', result.output)
Belita answered 8/11, 2018 at 13:56 Comment(6)
I try this, unexpected extra argument error disappeared. But result - self.assertEqual(0, result.exit_code) AssertionError: 0 != 1Motive
That means your code under test had an error. Inspect the result for the error message.Belita
What should I replace this code with:` --name url --filename good'.split()` ?Motive
That is what is passed in. Basically that is what would normally be in the list sys.argv. So what do you want to send in to test your program? Put it there.Belita
Then I added print(result) before self.assert I saw an error in the console <Result TypeError("find() got multiple values for argument 'name'",)>Motive
So your command line is wrong. Sounds like the testing is working perfectly.Belita
S
2

For those wanting to test exceptions in a click command, I have found this way to do it:

def test_download_artifacts(
        self,
    ):
        runner = CliRunner()
        # test exception raised for invalid dir format
        result = runner.invoke(
            my_module.download_artifacts,
            '--bucket_name my_bucket \
            --artifact_dir artifact_dir'.split(),
            input='2')
        print(f"result.exception: {result.exception}")
        assert "Enter artifact_dir ending" in str(result.exception)
Skier answered 20/12, 2020 at 5:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.