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

James Chapman james at uplinkzero.com
Thu Jan 16 06:08:19 PST 2014


Thank you Patrick.

This does help as it gives me a working start point to go from. Prior to
your message I was being caught out by the line:

subprocess.Popen.return_value.returncode = 0

so cheers for that.


--
James




On 16 January 2014 13:32, Patrick Smith <pjs482 at gmail.com> wrote:

> > 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/f39ceafc/attachment-0001.htm>


More information about the testing-in-python mailing list