ShockDoctor
ShockDoctor

Reputation: 683

How efficiently unit test a function with multiple input calls?

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

Answers (1)

gilch
gilch

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

Related Questions