[TIP] Fwd: Mocking with "mock" in unit testing

Ned Batchelder ned at nedbatchelder.com
Thu Jan 16 03:04:22 PST 2014


On 1/16/14 5:40 AM, James Chapman wrote:
> Originally sent to python tutor mailing list but I suspect this might 
> be a better list for my question...
>
> Hi all
>
> I have a question regarding mocking in unit testing.
>
> Let's assume I have the following class:
>
> -------------------------------------------
> import subprocess
>
> class Pinger(object):
>
>     def ping_host(self, host_to_ping):
> cmd_string = 'ping %s' % (host_to_ping)
> cmd_args = cmd_string.split()
>         proc = subprocess.Popen(cmd_args, shell=True)
> proc.wait()
>         if proc.returncode != 1:
> raise Exception('Error code was: %d' % (proc.returncode))
>
> -------------------------------------------
>
>
> In my unittest I don't want to run the ping command, (It might not be 
> available on the build system) I merely want to check that a call to 
> subprocess.Popen is made and that the parameters are what I expect?
>
> So far I have this, but it doesn't work and I suspect it's way off!!
>
>
> -------------------------------------------
> import mock
> import unittest
> from tutor_q import Pinger
>
> class Test_Pinger(unittest.TestCase):
>
>     def test_ping_host(self):
> pinger = Pinger()
> assert pinger
> subprocess = mock.Mock()
> subprocess.Popen.return_value = 0
> subprocess.assert_called_once_with(['ping','localhost'])
> pinger.ping_host('127.0.0.1')
>
Often the trickiest thing about mocking is mocking the right name. Here 
you've created a mock, and it's assigned to a local variable named 
subprocess.  This name is not related to the global subprocess name in 
the tutor_q module, which is the name that will be used by your pinger 
object.

You have to assign to that global.  A module's globals are also 
available as attributes on the module object, so you can do it like this:


import mock
import unittest
import tutor_q

class Test_Pinger(unittest.TestCase):

     def test_ping_host(self):
         pinger = tutor_q.Pinger()
         assert pinger
         subprocess = mock.Mock()
subprocess.Popen.return_value = 0
tutor_q.subprocess = subprocess
         pinger.ping_host('127.0.0.1')
subprocess.assert_called_once_with(['ping','localhost'])

I've also moved the assert to after the code under test, since that's 
when the calls will be available to make assertions about. (I didn't 
actually test this code, if I got it wrong, someone on the list will 
school me!)

A problem with this code is that you replace tutor_q.subprocess with a 
mock, but you don't restore it when the test is done.  Mock has tools 
that can do this for you:


     def test_ping_host(self):
         pinger = tutor_q.Pinger()
         assert pinger
         with mock.patch("tutor_q.subprocess") as subprocess:
             subprocess = mock.Mock()
subprocess.Popen.return_value = 0
pinger.ping_host('127.0.0.1')
subprocess.assert_called_once_with(['ping','localhost'])

Note that there is an unfortunate coupling of your test code and product 
code: the test had to know how subprocess was imported.  In tutor_q.py, 
if you had instead used "from subprocess import Popen", then your mock 
would have to be similarly adjusted to mock tutor_q.Popen instead of 
tutor_q.subprocess.

--Ned.
>
>
> if __name__ == '__main__':
> unittest.main()
>
> -------------------------------------------
>
>
> Can anyone point me in the right direction on how to mock up these 
> subprocess calls?
>
> Thanks
>
> James
>
>
>
>
>
>
> _______________________________________________
> testing-in-python mailing list
> testing-in-python at lists.idyll.org
> http://lists.idyll.org/listinfo/testing-in-python

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.idyll.org/pipermail/testing-in-python/attachments/20140116/4cd40075/attachment.htm>


More information about the testing-in-python mailing list