[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