0

This is the function that I have and it takes user input then further calls other functions depending on the input.

I should test whether the input is one of those 2 characters a or b.

def display_menu() -> str:
    """
    Start menu that directs the user wether
    a new or existing option is chosen
    """
    clrscr()
    program_title()
    print("Hello and Welcome are you: \n")
    status = input("a)New User\nb)Exsisting user \n")
    while status not in ("a", "b"):
        print(Fore.RED + "Please choose between a or b")
        status = input("a)New User\nb)Existing user \n")
    if status == "a":
        new_user()
    elif status == "b":
        exsisting_user()

    return status

And this is what I got so far

import unittest
from unittest.mock import patch

from validate import display_menu,new_user

class TestValidate(unittest.TestCase):
    """
    testing  for user input   
    between two options offered
    """
    def test_a(self):
        input = __builtins__.input
         __builtins__.input = lambda _: 'a'
        self.assertTrue('please enter your username')
Gino Mempin
  • 25,369
  • 29
  • 96
  • 135
  • 2
    Try looking at [How To Use unittest to Write a Test Case for a Function in Python](https://www.digitalocean.com/community/tutorials/how-to-use-unittest-to-write-a-test-case-for-a-function-in-python). – Alias Cartellano Oct 18 '22 at 21:09
  • 2
    Also see [Using unittest.mock to patch input() in Python 3](https://stackoverflow.com/q/18161330/2745495) on how to properly mock/patch the builtin `input`. – Gino Mempin Oct 18 '22 at 22:51
  • The more I read less I know.still didn't find an answer.but thnx – user18816965 Oct 23 '22 at 00:37

1 Answers1

0

If the goal of the tests is to check that passing a calls new_user(), passing b calls existing_user(), and passing neither one results in an infinite loop until the user passes the correct one, then you can write tests that:

  1. Patches the input function to simulate user input (a, b, and some invalid inputs for error cases) but not really requiring user inputs (since the goal of writing unit tests is to have automated tests)
  2. Patches the new_user() and existing_user() functions to not call the actual functions, but only intercept and check that they were actually called

(My answer here will ignore clrscr(), program_title(), the print-ing of "Hello and Welcome are you: \n", and the colorama Fore.Red outputs, as that's irrelevant to the input a and b, which, as I understand it, is what your question is all about.)

Patching the input function

Your attempt

input = __builtins__.input
__builtins__.input = lambda _: 'a'

kind of works, but that's not the correct way since that directly or permanently replaces the built-in input function. Your tests would need to remember to "un-patch" it so that it doesn't affect the other tests. Patching something should ideally only be done as part of the test scope, and should not "leak" into other tests.

From the unittest docs:

...mock provides a patch() decorator that handles patching module and class level attributes within the scope of a test...

patch() acts as a function decorator, class decorator or a context manager. Inside the body of the function or with statement, the target is patched with a new object. When the function/with statement exits the patch is undone.

...and that's how the answers in Using unittest.mock to patch input() in Python 3 describe how to do it: use unittest.mock.patch. Following those answers, patch the input call to return either a or b, call display_menu(), then assert that display_menu's return value matches the simulated input:

import unittest
from unittest.mock import patch

from validate import display_menu

class TestValidate(unittest.TestCase):

    @patch("builtins.input", return_value="a")
    def test_a(self, patched_input):
        status = display_menu()
        self.assertEqual(status, "a")

    @patch("builtins.input", return_value="b")
    def test_b(self, patched_input):
        status = display_menu()
        self.assertEqual(status, "b")

if __name__ == "__main__":
    unittest.main()
$ python -m unittest -v tests/test_validate.py
test_a (tests.test_validate.TestValidate) ... ok
test_b (tests.test_validate.TestValidate) ... ok

----------------------------------------------------------------------
Ran 2 tests in 0.001s

OK

Patching the new_user()/existing_user() functions

Next, you want to know if display_menu calls the right functions based on the input. You can use unittest.mock.patch again on those, which returns a MagicMock object. One of the methods of a MagicMock is assert_called which

Assert that the mock was called at least once

(There are other varieties of assert_*, such as for checking how many times the mock was called and how it was called.)

So, assuming both methods are defined in the same validate.py module (validate.new_user, validate.existing_user), same as what you did for input, patch them like so:

import unittest
from unittest.mock import patch

from validate import display_menu

class TestValidate(unittest.TestCase):
    @patch("validate.new_user")
    @patch("builtins.input", return_value="a")
    def test_a(self, patched_input, patched_new_user):
        status = display_menu()

        self.assertEqual(status, "a")
        patched_new_user.assert_called()

    @patch("validate.existing_user")
    @patch("builtins.input", return_value="b")
    def test_b(self, patched_input, patched_existing_user):
        status = display_menu()

        self.assertEqual(status, "b")
        patched_existing_user.assert_called()

if __name__ == "__main__":
    unittest.main()
$ python -m unittest -v tests/test_validate.py
test_a (tests.test_validate.TestValidate) ... ok
test_b (tests.test_validate.TestValidate) ... ok

----------------------------------------------------------------------
Ran 2 tests in 0.001s

OK

(If they are not defined in the same validate.py file, then adjust the test accordingly, specifically the patch("validate.existing_user") part which says "patch 'existing_user' from package/module 'validate".)

If you want to be doubly certain that when new_user is called, existing_user isn't (and vice-versa), simply patch both in the tests and assert accordingly:

class TestValidate(unittest.TestCase):
    @patch("validate.existing_user")
    @patch("validate.new_user")
    @patch("builtins.input", return_value="a")
    def test_a(self, patched_input, patched_new_user, patched_existing_user):
        status = display_menu()

        self.assertEqual(status, "a")
        patched_new_user.assert_called()
        patched_existing_user.assert_not_called()

    @patch("validate.existing_user")
    @patch("validate.new_user")
    @patch("builtins.input", return_value="b")
    def test_b(self, patched_input, patched_new_user, patched_existing_user):
        status = display_menu()

        self.assertEqual(status, "b")
        patched_new_user.assert_not_called()
        patched_existing_user.assert_called()

Patching for incorrect input

The trickiest part to test here is the while loop to handle invalid inputs. Same as before, you need to patch the built-in input, but patch it in a way that it simulates repeated/multiple invalid user inputs (ex. x, y, z) and then ends with either an a or a b, in which case it should end up calling either new_user or existing_user.

This is done by patch-ing input with a MagicMock with a side_effect:

Using side_effect to return a sequence of values:

>>> mock = Mock()
>>> mock.side_effect = [3, 2, 1]
>>> mock(), mock(), mock()
(3, 2, 1)

Like so:

class TestValidate(unittest.TestCase):
    ...

    @patch("validate.existing_user")
    @patch("validate.new_user")
    @patch("builtins.input", side_effect=["x", "y", "z", "a"])
    def test_invalid(self, patched_input, patched_new_user, patched_existing_user):
        status = display_menu()

        self.assertEqual(status, "a")
        patched_new_user.assert_called_once()
        patched_existing_user.assert_not_called()
$ python -m unittest -v tests/test_validate.py
...
test_invalid (tests.test_validate.TestValidate) ... Please choose between a or b
Please choose between a or b
Please choose between a or b
ok

----------------------------------------------------------------------
Ran 3 tests in 0.003s

OK

Notice from the test logs that it prints out the message from the while loop 3 times. That doesn't look good, so, if you want, same as what's done for input you can also patch the built-in print function:

class TestValidate(unittest.TestCase):
    ...

    @patch("validate.existing_user")
    @patch("validate.new_user")
    @patch("builtins.input", side_effect=["x", "y", "z", "a"]))
    @patch("builtins.print", autospec=True)
    def test_invalid(self, patched_print, patched_input, patched_new_user, patched_existing_user):
        status = display_menu()

        self.assertEqual(status, "a")

        # Expect to run the code inside the loop 3 times
        self.assertEqual(patched_print.call_count, 3)

        # Expect that the last user input `a` calls `new_user`
        patched_new_user.assert_called_once()
        patched_existing_user.assert_not_called()
$ python -m unittest -v tests/test_validate.py
test_a (tests.test_validate.TestValidate) ... ok
test_b (tests.test_validate.TestValidate) ... ok
test_invalid (tests.test_validate.TestValidate) ... ok

----------------------------------------------------------------------
Ran 3 tests in 0.006s

OK

I highly recommend reading through the unittest docs to get familiar with all the functionalities it can provide.

Gino Mempin
  • 25,369
  • 29
  • 96
  • 135
  • Thank you Gino. this seems much clearer now i will certainly got trough the docs cause i got so much to learn, now i got some base to work with. – user18816965 Oct 23 '22 at 21:25
  • tests are running fine except the first one where im assertingEqual that status(function display_menu()) when input is "a" is equal to "a" im getting infinite printing of new_user function where im asking for username input first and it goes ` while True: username = input("Please enter your username: \n ") if not re.match(r'^[a-zA-z0-9]{2,12}$', username): print( Fore.RED + 'userame must be 2 to 12 characters long\ and contain letters and numbers') continue break` im getting that print infinite? – user18816965 Oct 23 '22 at 23:15
  • So basicaly i dont need those 2 functions to do anything( new_user and existing user) since i will be testing only if certain input is calling certain function and maybe like you said that calls one function only( so when one is called other one id not) cause now in first test right func is called but it throws infinite string like input in in called function from the patched input is invalid) if i leave those 2 functions to do nothing first test should be ok. – user18816965 Oct 23 '22 at 23:49