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:
- 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)
- 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.