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
10
  • Is it obvious? What happens instead? How is "We are being attacked! Attack Back!" related to "Hello Pirate!"? Commented Sep 2, 2014 at 22:06
  • Oops. My bad. Totally forgot to change that. But regardless its still not working for me. I edited my code. @jonrsharpe Commented Sep 2, 2014 at 22:15
  • Are you sure the right "main()" is getting called in your test? What does happen? It just fails on the assert? Commented Sep 2, 2014 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 Commented Sep 2, 2014 at 22:37
  • Try adding a parameter to your test, like this: def test_output(self, random): And see what it does. Commented Sep 2, 2014 at 22:40

2 Answers 2

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.

Sign up to request clarification or add additional context in comments.

2 Comments

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.
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.
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!")

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.