1

I'm currently creating some unit test. I'm fairly new to them and just trying to get my feet wet. So the current test I'm trying to run is to check for an expected output according to the users input. So I would patch the input with some type of value and then check if I received the stdout message at the end. Sounds kind of confusing, but I hope some one can help. Here is my run code.

def main():


  Attack = input("Are we being attacked?!")

  if(Attack == "yes"):
    print("We are being attacked! Attack Back!")

so in the above example I would test for the print statement since I would be patching the user input with the value of yes. Here is my test suite

import unittest
from unittest.mock import patch
import io
import sys

from RunFile import main

class GetInputTest(unittest.TestCase):

  @patch('builtins.input', return_value='yes')
  def test_output(self):
      saved_stdout = sys.stdout
      try:
          out = io.StringIO()
          sys.stdout = out
          main()
          output = out.getvalue().strip()
          self.assertEqual(output, "We are being attacked! Attack Back!")
      finally:
          sys.stdout = saved_stdout


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

So this obviously doesn't work. So what am I missing? Thank you all in advance!

EDITED: Here is the error message I get when I run the test. I understand the error, just don't know how I would go about fixing it.

Error
Traceback (most recent call last):
  File "C:\Python33\lib\unittest\mock.py", line 1087, in patched
    return func(*args, **keywargs)
TypeError: test_output() takes 1 positional argument but 2 were given
salce
  • 409
  • 1
  • 12
  • 28
  • Is it obvious? What happens instead? How is `"We are being attacked! Attack Back!"` related to `"Hello Pirate!"`? – jonrsharpe Sep 02 '14 at 22:06
  • Oops. My bad. Totally forgot to change that. But regardless its still not working for me. I edited my code. @jonrsharpe – salce Sep 02 '14 at 22:15
  • Are you sure the right "main()" is getting called in your test? What does happen? It just fails on the assert? – shieldstroy Sep 02 '14 at 22:25
  • I edited my code to import main specifically. I also included the error message I got when I ran the code. I understand what its telling me, but don't know how to fix it. @troylshields – salce Sep 02 '14 at 22:37
  • Try adding a parameter to your test, like this: def test_output(self, random): And see what it does. – shieldstroy Sep 02 '14 at 22:40
  • It passes, but passes regardless of the stdout. Which is what I'm trying to check. So I'm not sure what output its checking @troylshields – salce Sep 02 '14 at 22:52
  • @Salce__ try using the `unittest` asserts rather than asserting yourself. `self.assertEqual(output, "We are being attacked! Attack Back!")` – Adam Smith Sep 02 '14 at 22:56
  • @AdamSmith awesome that worked. Thanks! Would love to give you credit if you post it as an answer. I updated my code with the final solution. – salce Sep 02 '14 at 23:03
  • @Salce__ no time atm to write up anything that long. You could also have `patch`ed `'builtins.print'` and done `new_print.assert_called_with("We are being attacked! Attack Back!")` rather than monkey with trying to reassign stdout – Adam Smith Sep 02 '14 at 23:08
  • @Salce__ I wrote up my answer, and also another way of patching that makes it much easier. – Adam Smith Sep 03 '14 at 02:26

2 Answers2

2

A function decorated by patch will take the Mock as an additional parameter. You need

@patch('builtins.input', return_value='yes')
def test_output(self, m):

where the second argument m will be a reference to the Mock object that replaces input when test_output is called.

From pydoc unittest.mock under patch:

If patch is used as a decorator and new is omitted, the created mock is passed in as an extra argument to the decorated function.

chepner
  • 497,756
  • 71
  • 530
  • 681
  • Okay I tried this, and it kinda worked... it gave me the "Ok", but it passes the test regardless of my stdout so basically the expected stdout =! actual stdout and still passes. – salce Sep 02 '14 at 22:51
  • It seems to be working for me. If I change the mock's return value to alter what `main` does, or change the string `main` prints, the test fails. – chepner Sep 03 '14 at 12:32
2

In addition to @chepner's answer, you'll need to use unittest.TestCase's assert methods rather than asserting yourself (pun intended)

class TestStuff(unittest.TestCase):
    @patch('builtins.input', return_value='yes')
    def test_output(self, new_input):
        try:
            out = io.StringIO()
            sys.stdout = out
            main()
            output = out.getvalue().strip()
            self.assertEqual(output, "We are being attacked! Attack Back!")
        finally:
            sys.stdout = saved_stdout

However that's probably not the best way to do what you're trying to do. You can patch more than one builtin, you know!

class TestStuff(unittest.TestCase):
    @patch('builtins.input', return_value='yes')
    @patch('builtins.print')
    def test_output(self, new_print, new_input):
        # the mocked functions are passed in opposite order
        # to where they're decorated
        main()
        new_print.assert_called_with("We are being attacked! Attack Back!")

If the decorators are scary, you could even do:

class TestStuff(unittest.TestCase):
    def test_output(self):
        with patch('builtins.input', return_value='yes'), \
             patch('builtins.print') as new_print:
            main()
            new_print.assert_called_with("We are being attacked! Attack Back!")
Adam Smith
  • 52,157
  • 12
  • 73
  • 112