Ingo
Ingo

Reputation: 736

How to mock socket module in python?

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

Answers (1)

Will Keeling
Will Keeling

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

Related Questions