0

I'm trying to use and test sockets in python (I have to use 3.5 version).

Consider this code:

import socket

def send_recv(xml_message):
    try:
        address = ('127.0.0.1', 12000)
        xml_bytes = xml_message.encode(encoding="utf-8")
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client_socket:
            client_socket.settimeout(10) 
            client_socket.connect(address)
            client_socket.sendall(xml_bytes)
            reply = ''
            while True:
                data = client_socket.recv(1024).decode()
                print(data)
                reply += data
                if "<!--MessageEnd-->" in reply or not data:
                    break
            client_socket.shutdown(socket.SHUT_WR)
        return reply
    except ConnectionError as e:
        error_description = "Error connecting to TCP/IP: {}".format(e) 
        print(error_description)
        return None
    except socket.timeout:
        error_description = "Error receiving message. No reply received or message termination not found." 
        print(error_description)
        return None

I tried to test the case where I receive an empty string as a response. I wrote this code:

import unittest
from unittest.mock import patch, MagicMock
from socket_example import only_recv, send_recv

class TestSocketExample(unittest.TestCase):

    @patch('socket_example.socket.socket')
    def test_send_recv(self, mock_socket):
        mock_socket = mock_socket.return_value
        mock_socket.recv.return_value = b''
        #TODO: I have to insert an assert to check the result

I immediately encountered two problems:

  1. If I start the test, the print of the data received

    print(data)

prints the following thing

<MagicMock name='socket().__enter__().recv().decode()' id='137547990704080'>

instead of the empty string.

  1. In the code to be tested I inserted the following code

    client_socket.settimeout(10)

which seems to be ignored because I have to forcefully stop the test.

I don't understand the reason for these code behaviors.

3
  • 2
    The easiest thing to do is not mock anything. Instead of hard-coding the information used to create the socket inside the function, have the function take a socket as an argument, then pass a socket-like object to the function for testing. Your function is primarily concerned with interacting with a socket(-like object), not creating a network connection. Commented Dec 16, 2024 at 16:08
  • (Or rather, not patch anything. The socket-like object you pass to the modified function could be a Mock object, or some other stub, or even a real socket, if you want to set up a test fixture involving an actual TCP server.) Commented Dec 16, 2024 at 16:28
  • Technically, though, the problem is that client_socket is not what socket.socket.__enter__ returns. In "real life", __enter__ returns self, but in mock-land it's a different object and needs to be accounted for. Commented Dec 16, 2024 at 16:31

1 Answer 1

0

You need to account for the fact that client_socket is the return value of the socket's __enter__ method, not necessarily the actual socket. (In real life it is, but the patching system doesn't know that.)

mock_socket.__enter__.return_value.recv.return_value = b''

To test the timeout, you need to define a side effect for the fake socket, which makes recv raise an exception instead of returning any value.

def timeout(*args):
    raise socket.timeout

mock_socket.__enter__.return_value.recv.side_effect = socket.timeout()

The side_effect attribute can take a list of values to return/raise, to simulate one or more successful calls to recv before the timeout finally occurs. You can also assign a function to the attribute, which is called each time the mock is called, allowing you to raise an appropriate exception or return an appropriate value as required for your test.

Sign up to request clarification or add additional context in comments.

6 Comments

So __enter__ method is related to with statement. Is this correct?
Yes; whatever value you use as the "argument" to with must have an __enter__ method, which gets called (and its return value bound to the name associated with an optional as clause). See docs.python.org/3/reference/… for a "desugaring" of the with statement syntax to a roughly equivalent try statement.
Most importantly: with foo() as bar is not equivalent to bar = foo(); it's more like t = foo(); bar = t.__enter__().
Perfect. Do you have any suggestions for the settimeout problem? I confirm that it works in the code but it is ignored in the unit test.
Before your addition on the settimeout problem I had tried to write the following code: mock_socket.__enter__.return_value.settimeout.side_effect = socket.timeout() I had obtained the same practical result as your solution but I think your solution is better because the object of my tests is actually the management of the result of the recv method rather than the management of settimeout
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.