3

The test for the following method checks that the stdout in prompt is correct.

When 'input()' gets called it waits for the user to press enter and interrupt. The test is passing by auto keypress-ing 'enter'.

This is hacky and there must be a better way of testing this method.

METHOD:

class GameDisplay:

@staticmethod
def prompt(text):
    input_value = input(text)
    return input_value

TEST:

from pynput.keyboard import Key, Controller

class TestGameDisplay(unittest.TestCase):

@patch('sys.stdout', new_callable=StringIO)
def test_prompt_output(self, mock_stdout):
    keyboard = Controller()
    keyboard.press(Key.enter)
    self.gameDisplay.prompt('Choose 0: ')
    keyboard.release(Key.enter)
    self.assertEqual( mock_stdout.getvalue(), 'Choose 0: ')

if __name__ == '__main__':
   unittest.main()

OUTPUT:

Breaks line as a result of pressing enter.

...................................
............................................
----------------------------------------------------------------------
Ran 79 tests in 0.113s

OK

TEST FIX ATTEMPT:

@patch('sys.stdout', new_callable=StringIO)
@patch('builtins.input', return_value='0')
def test_prompt_output(self, mock_stdout, input):
    self.gameDisplay.prompt('Choose 0: ')
    self.assertEqual( mock_stdout.getvalue(), 'Choose 0: ')

OUTPUT:

FAIL: test_prompt_output (tests.test_game_display.TestGameDisplay)
self.assertEqual( mock_stdout.getvalue(), 'Choose 0: ')
AssertionError: <MagicMock name='input.getvalue()' id='4675607744'> != 'Choose 0: '

----------------------------------------------------------------------
Ran 79 tests in 0.015s

FAILED (failures=1)
Huasmc
  • 63
  • 1
  • 6
  • Try swapping the order of the decorators? – Alex Hall May 16 '18 at 21:26
  • @AlexHall Getting the following output when swapping decorators: `AssertionError: '' != 'Choose 0: '` – Huasmc May 16 '18 at 21:31
  • 1
    Maybe just print the prompt separately and then use `input()` with no prompt? – Alex Hall May 16 '18 at 21:33
  • This can work but two functions would have to be called separately to perform one task. – Huasmc May 17 '18 at 07:10
  • Swapping the decorators was a step in the right direction. You then also have to patch `input` with something that prints to stdout. The original input isn't being called so there's nothing left that prints to stdout, hence its value is the empty string. – Alex Hall May 17 '18 at 20:09

1 Answers1

0

mock_stdout only catch the stdout of print statement, so add print(text)

class GameDisplay:

    @staticmethod
    def prompt(text):
        input_value = input(text)
        print(text)
        return input_value

This piece of code is tested and worked as expected

@mock.patch('sys.stdout', new_callable=StringIO)
@mock.patch('__builtin__.input', return_value='0')
def test_prompt_output3(self, mock_input, mock_stdout):
        p = GameDisplay.prompt('Choose 0: ')
        self.assertEqual(p, '0')
        self.assertEqual(mock_stdout.getvalue(), 'Choose 0: \n')

but then it would print Choose 0: twice

Test the return_value of prompt() should be enough in real life.

There is only 1 option left if you really want to mock input and output at the same time.

This will do, as long as you do not mock the print at the same time:

print(text)
input_value = input()

Or go for a insane way, fake the stdout:

mock_stdout = StringIO('Choose 0: ')
p = GameDisplay.prompt('Choose 0: ')
self.assertEqual(mock_stdout.getvalue(), 'Choose 0: ')

or this is similar to assign a return_value to mock object:

with mock.patch('sys.stdout', new=StringIO("xxx")) as mock_stdout:
Gang
  • 2,658
  • 3
  • 17
  • 38
  • This is a good alternative but then it would print `Choose 0: ` twice. Once when `input()` gets called and the second time when `print()` gets called. – Huasmc May 17 '18 at 07:04
  • @Huascar, this is not reasonable in real life, I add a fake everything answer. – Gang May 17 '18 at 22:48