简体   繁体   中英

Unit test failing when mocking python3

I am trying mock python3 requests

Here is my unit test:

from source.automation.my_web_session import MyWebSession
from unittest.mock import patch

@patch('requests.Session', autospec=True)
def test_initialize_session(mock_session):
    # Arrange
    user_agent = 'mobile user agent'
    mock_session.headers = {'user-agent' : user_agent}
    csrftoken = 'csrftoken'
    mock_session.cookies = {'csrftoken' : csrftoken}
    my_web_session = MyWebSession()
    # Act
    print(my_web_session.session.cookies)
    # Assert
    assert my_web_session.session.cookies['csrftoken'] == csrftoken

Here is the class I am testing:

import requests

class MyWebSession:

    def __init__(self):
        self.session = requests.Session()

I am trying to mock the setting and updating of the user agent. However, running my test I get the following error:

================================== FAILURES ===================================
___________________________ test_initialize_session ___________________________

mock_session = <MagicMock name='Session' spec='Session' id='81084944'>

    @patch('requests.Session', autospec=True)
    def test_initialize_session(mock_session):
        # Arrange
        user_agent = 'mobile user agent'
        mock_session.headers = {'user-agent' : user_agent}
        csrftoken = 'csrftoken'
        mock_session.cookies = {'csrftoken' : csrftoken}
        my_web_session = MyWebSession()
        # Act
>       print(my_web_session.session.cookies)

my_web_session_test.py:14:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <NonCallableMagicMock name='Session()' spec='Session' id='80148752'>
name = 'cookies'

    def __getattr__(self, name):
        if name in {'_mock_methods', '_mock_unsafe'}:
            raise AttributeError(name)
        elif self._mock_methods is not None:
            if name not in self._mock_methods or name in _all_magics:
>               raise AttributeError("Mock object has no attribute %r" % name)
E               AttributeError: Mock object has no attribute 'cookies'

What I am doing wrong here?

The problem is that you are configuring the class ( mock_session replaces Session ) but you want to configure the instance, which is its return value. You can do that but it won't make a nice test. This works for me:

@patch('requests.Session', autospec=True)
def test_initialize_session(mock_session_cls):
    # Arrange
    mock_session = Mock(spec=[])
    user_agent = 'mobile user agent'
    mock_session.headers = {'user-agent' : user_agent}
    csrftoken = 'csrftoken'
    mock_session.cookies = {'csrftoken' : csrftoken}
    mock_session_cls.return_value = mock_session
    my_web_session = MyWebSession()
    # Act
    print(my_web_session.session.cookies)
    # Assert
    assert my_web_session.session.cookies['csrftoken'] == csrftoken

Some people might prefer the version below (I don't like it either):

def test_initialize_session():
    # Arrange
    mock_session = Mock(spec=[])
    user_agent = 'mobile user agent'
    mock_session.headers = {'user-agent' : user_agent}
    csrftoken = 'csrftoken'
    mock_session.cookies = {'csrftoken' : csrftoken}
    with patch('requests.Session', autospec=True, return_value=mock_session):
        my_web_session = MyWebSession()
    # Act
    print(my_web_session.session.cookies)
    # Assert
    assert my_web_session.session.cookies['csrftoken'] == csrftoken

What I find ugly about this test is that you are stubbing a test double to return another test double while you are interested in the second one only. That looks like one too many test doubles to me.

The underlying issue is that the creation of the Session instance is hidden inside the __init__ method as an implementation detail, yet your test wants to control it as if it were public API. One way of solving this tension is creating the Session outside MyWebSession and passing the instance as a parameter. Then the test becomes more straightforward, you can get rid of the patching and the test double that you are not really interested in.

class MyWebSession:
    def __init__(self, session):
        self.session = session

And the test:

def test_initialize_session():
    # Arrange
    user_agent = 'mobile user agent'
    mock_session = Mock(spec=[])
    mock_session.headers = {'user-agent' : user_agent}
    csrftoken = 'csrftoken'
    mock_session.cookies = {'csrftoken' : csrftoken}
    my_web_session = MyWebSession(mock_session)
    # Act
    print(my_web_session.session.cookies)
    # Assert
    assert my_web_session.session.cookies['csrftoken'] == csrftoken

In the production code you would use something like

my_web_session = MyWebSession(requests.Session())

Another way is looking at MyWebSession as a thin wrapper providing a more convenient interface to requests.Session (if that is what you are trying to do here), then you might think of the tests as characterization tests and not use test doubles at all.

Note that the # Act comment is misplaced and the call to print is irrelevant for the test (maybe you put it there for debugging?). That does not help but I left it as it is in your code to deviate as less as possible from it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM