Reputation: 736
I am working on a multicast project with python3
and have a small program to search for upnp
devices on the local network. I want to use unittest
and mock
the expensive call to socket.recvfrom(bufsize[, flags])
.
Here is my program ./upnp/mucassdp.py
:
#!/usr/bin/env python3
import socket
class ssdpObj():
# Handle Simple Service Discovery Protocol,
# for discovering UPnP devices on the local network
def msearch(self):
msg = \
'M-SEARCH * HTTP/1.1\r\n' \
'HOST:239.255.255.250:1900\r\n' \
'ST:upnp:rootdevice\r\n' \
'MX:2\r\n' \
'MAN:"ssdp:discover"\r\n' \
'\r\n'
# Set up UDP socket with timeout and send a M-SEARCH structure
# to the upnp multicast address and port
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
s.settimeout(2)
s.sendto(msg.encode(), ('239.255.255.250', 1900) )
# print received data within the timeout
try:
while True:
# expensive call to mock for testing
data, addr = s.recvfrom(65507)
print (addr, data.decode(), end='')
except socket.timeout:
pass
And here is my test case ./tests/mucassdpTest.py
:
#!/usr/bin/env python3
import unittest
from unittest.mock import patch
from upnp.mucassdp import ssdpObj
class mucassdpTestCase(unittest.TestCase):
@patch('upnp.mucassdp.socket')
def test_msearch(self, mock_socket):
# instantiate our service
oSsdp = ssdpObj()
mock_socket.recvfrom.return_value = [0, '1']
#mock_socket.recvfrom.side_effect = [0, '1']
oSsdp.msearch()
mock_socket.settimeout.assert_called_with(2)
When I try to run the test case with:
~$ python3 -m unittest tests.mucassdpTest
I get the error message (only relevant part):
ERROR: test_msearch (tests.mucassdpTest.mucassdpTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/ingo/devel/muca-tools/upnp/mucassdp.py", line 27, in msearch
data, addr = s.recvfrom(65507)
ValueError: not enough values to unpack (expected 2, got 0)
I've looked around and found some similar questions and answers, e.g. initialize Mock
through its constructor with the expected data, addr
or setup side_effect
( I've tried this) but I cannot get it to run successfully with my test.
How do I have to set up the patched socket
module to get the expected paired data, addr
from s.recvfrom(...)
under test conditions?
Upvotes: 3
Views: 3822
Reputation: 23004
The issue here is that you are mocking the socket
module import in upnp.mucassdp
, and then trying to configure it with the recvfrom()
call. However, the recvfrom()
method exists on the socket.socket
class, not the socket
module.
@patch('upnp.mucassdp.socket')
def test_msearch(self, mock_socket):
# mock_socket is the module, not the socket class
The way to resolve is to patch the socket.socket
class, and then in the test, retrieve the instance before configuring it.
class mucassdpTestCase(unittest.TestCase):
@patch('upnp.mucassdp.socket.socket') # Patch the class
def test_msearch(self, mock_socket):
mock_socket = mock_socket.return_value # We want the instance
# instantiate our service
oSsdp = ssdpObj()
mock_socket.recvfrom.return_value = [0, '1']
#mock_socket.recvfrom.side_effect = [0, '1']
oSsdp.msearch()
mock_socket.settimeout.assert_called_with(2)
Upvotes: 4