2

I want to test user input confirmation answer in a custom management command. The test is for message displayed to user and answer that she enters.

The commands' code is:

class Command(BaseCommand):

    def add_arguments(self, parser):
        parser.add_argument('id', type=int)

    def handle(self, *args, **options):
        try:
            experiment = Experiment.objects.get(pk=options['id'])
        except Experiment.DoesNotExist:
            raise CommandError(
                'Experiment with id "%d" does not exist' % (options['id'])
            )

        answer = input('Are you sure? (Y/n)')
        if answer == 'Y':
            experiment.delete()

This accepted answer suggests to use mocking, but it's in a lonely context. I want to test the user input as well other things that I could add to the custom command.

What would be an efficient approach?

Caco
  • 1,601
  • 1
  • 26
  • 53

1 Answers1

3

After searching several sources, I could not find one solution that was similar to my problem. So I mixed some of them to arrive to a neat solution using python mock library.

Test method (in test_commands.py):

from unittest.mock import patch
# other imports

@patch('experiments.management.commands.remove_experiment.get_input',
           return_value='Y')
    def test_remove_experiment_displays_prompt_and_user_confirm_removing(
            self, mock_user_input
    ):
        experiment = create_experiment()
        out = StringIO()
        call_command(
            'remove_experiment', experiment.id, stdout=out
        )

        self.assertEqual(mock_user_input.called, True)
        (text,), kwargs = mock_user_input.call_args
        self.assertEqual(text,
            'All versions of experiment "%s" will be destroyed and cannot be '
            'recovered. Are you sure? (Yes/n) ' % experiment.title)
        self.assertFalse(Experiment.objects.exists())

Now, in command Class we wrap python input() in our method (like did in accepted answer mentioned in the question).

my_app.management.commands.remove_experiment:

def get_input(text):
    return input(text)

class Command(BaseCommand):

    def add_arguments(self, parser):
        parser.add_argument('id', type=int)

    def handle(self, *args, **options):
        try:
            experiment = Experiment.objects.get(pk=options['id'])
        except Experiment.DoesNotExist:
            raise CommandError(
                'Experiment with id "%d" does not exist' % (options['id'])
            )

        answer = get_input('Are you sure? (Y/n)')
        if answer == 'Y':
            experiment.delete()

Now, the test will verify the question text, in user input prompt is correct, but will not display it in stdout. Besides, the kwarg return_value='Y' in @patch context decorator will simulate the user answer, and the test passes.

Caco
  • 1,601
  • 1
  • 26
  • 53