Reputation: 683
I have a unit test that works as intended, however I feel that this is not the best way to test multiple inputs with pytest. It definitely violates the DRY principle. I thinking there's a better way to go about this but I can't figure out what. I'm also not sure what to actually do with the mock. It's not used but it has to be there (see 'mock_choice' parameter in the function in the code below).
I thought perhaps looping through the calls would work but that didn't work as intended. I really couldn't figure out another way besides using side_effects and calling four times to test to make sure I get return value as I intended.
Function To Test
def export_options():
while True:
try:
choice = int(input("\nPlease make a selection"))
if choice in ([option for option in range(1, 5)]):
return choice # This what I'm testing
else:
print("\nNot a valid selection\n")
except ValueError as err:
print("Please enter an integer")
Test Function
@mock.patch('realestate.app.user_inputs.input', side_effect=[1, 2, 3, 4])
def test_export_options_valid_choice(mock_choice): # mock_choice needs to be here but isn't used!
export_option = user_inputs.export_options()
assert export_option == 1
export_option = user_inputs.export_options()
assert export_option == 2
export_option = user_inputs.export_options()
assert export_option == 3
export_option = user_inputs.export_options()
assert export_option == 4
The test works. It passes as the function returns all values between 1 and 4. However, because the code is very repetitive, I would like to know if there's a better way to test multiple input calls as I would like to apply the same to future tests.
Upvotes: 1
Views: 2712
Reputation: 11651
You can use a for loop to avoid repeating code.
@mock.patch('realestate.app.user_inputs.input')
def test_export_options_valid_choice(self, mock_choice):
for response in [1, 2, 3, 4]:
with self.subTest(response=response)
mock_choice.return_value = response
export_option = user_inputs.export_options()
self.assertEqual(export_option, response)
# Not in original version, but other tests might need it.
mock_choice.assert_called_once_with("\nPlease make a selection")
mock_choice.reset_mock()
The subtest context manager will tell you which input failed.
Is the only way to do something like this is with sub-test using the unittest module? I know pytest doesn't support subtests, but I was hoping there was a similar type of hack.
You can certainly loop without using subtests, but you may have difficulty telling which input failed. More generally, instead of a loop, you can call a common helper function for each test.
For pytest in particular, you can use the @pytest.mark.parametrize
decorator to automate this.
@pytest.mark.parametrize('response', [1, 2, 3, 4])
@mock.patch('realestate.app.user_inputs.input')
def test_export_options_valid_choice(mock_choice, response):
mock_choice.return_value = response
export_option = user_inputs.export_options()
assert export_option == response
Upvotes: 2