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

James Chapman james at uplinkzero.com
Thu Jan 16 03:22:11 PST 2014


Thanks Ned and Tiemo (not sure your mail went to the list?). You're quite
right, I've not mocked the subprocess class which thinking about it is
really what I want to do and somehow inject that into the test so that when
I call Pinger it uses the mock subprocess class rather than the real one.
Although as of writing I'm still unsure how to do that?

The code I've used in the question is a simplified version of what I'm
actually doing. ping was substituted for a tool that will not be available
on a continuous integration build system. If I can understand and get a
simplified mock to work, then I'll hopefully be able to use my new found
knowledge to mock out the real test.

--
James


On 16 January 2014 11:04, Ned Batchelder <ned at nedbatchelder.com> wrote:

>  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 listtesting-in-python at lists.idyll.orghttp://lists.idyll.org/listinfo/testing-in-python
>
>
>
> _______________________________________________
> 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/050d5fd4/attachment-0001.htm>


More information about the testing-in-python mailing list