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

Patrick Smith pjs482 at gmail.com
Thu Jan 16 05:32:11 PST 2014


> 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.

That's exactly what you need to do and what `mock.patch` will accomplish
for you. Consider the following the test code:

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

The important line here is `with mock.patch("tutor_q.subprocess") as
subprocess:`. `mock.patch` will create a mock object and replace
`subprocess` in the `tutor_q` module with that mock object (and then
re-insert the real `subprocess` when the block is exited). That mock object
is then assigned to a variable named `subprocess` that you can use in your
tests (note: it can be assigned to any name you want, but using the same
name as what's being mocked out will make the tests more readable).

The line where you're setting the return value of the mock also needs to be
changed. If you do `subprocess.Popen.return_value = 0`, then in the
`ping_host` method, `proc` will be 0, since it's the result of calling
`subprocess.Popen`. So, you won't be able to do things like `proc.wait()`
or `proc.return_code` since those attributes don't exist on an integer.
 Instead, you can set the `return_code` on `subprocess.Popen.return_value`,
which is itself a Mock object.

Hope this helps!

- Patrick

On Thu, Jan 16, 2014 at 6:22 AM, James Chapman <james at uplinkzero.com> wrote:

> 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
>>
>>
>
> _______________________________________________
> 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/b61043b7/attachment.htm>


More information about the testing-in-python mailing list