Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python how to mock a function within another function

I have made some changes to a functionality which is breaking the unit tests. Previously i had a common.py containing the function request_url:

import requests

def request_url(method, url):
    return _request_url(method, url)

def _request_url(method, url):
    return requests.request(method, url)

And this was the test:

@mock.patch("requests.request", autospec=True)
def test_request_url_200(self, mock_request):

    response = mock.MagicMock()
    response.status_code = 200
    method = mock.sentinel.method
    path = "url"

    result = common.request_url(
        method,
        path
    )

    self.assertListEqual([
        mock.call(
            mock.sentinel.method,
            "url"
        ),
    ], list(mock_request.mock_calls))

    self.assertListEqual([mock.call.raise_for_status()], list(response.mock_calls))
    self.assertEqual(mock_request.return_value, result)

After the changes in functionality, instead of simply calling requests.request, i have first initiated a session, mounted a TLS Adapter and then called the session's request function like this:

def _request_url(method, url):
    session = requests.session()
    adapter = TlsAdapter(ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1)
    session.mount("https://", adapter)
    return response = session.request(method, url)

Here, i am not sure how to exactly mock session.request since that is available through the session variable. I tried patching requests.session.request but that does not work here.

Does anyone have any idea how can this function be mocked?

thanks!

like image 203
Hussain Ali Akbar Avatar asked Mar 05 '18 13:03

Hussain Ali Akbar


1 Answers

I think the reason in @mock.patch(...). You set autospec=True, but your mock_request doesn't return data(in your case everytime - Mock). The documentation says:

If you set autospec=True then the mock will be created with a spec from the object being replaced. All attributes of the mock will also have the spec of the corresponding attribute of the object being replaced. ...

Try to call:

print(mock_request.return_value) # <MagicMock name='request()()' id='4343989392'>
# or
# print(mock_request.return_value.return_value) # <MagicMock name='request()()()' id='4344191568'>

As you can see requests.request was 'mocked' but doesn't return any data. You can set expected data using return_value or side_effect. Here an example:

from unittest import TestCase
import mock
from common import request_url


class MyTestExample(TestCase):

    def test_request_url_1(self):
        mocked_request = mock.patch('requests.request', side_effect=['one', 'two', 'tree', ])
        mocked_request.start()
        # request_url(...) will return item from list
        self.assertEqual('one', request_url('test', 'url'))
        self.assertEqual('two', request_url('test', 'url'))
        self.assertEqual('tree', request_url('test', 'url'))

        mocked_request.stop()

    def test_request_url_2(self):
        result = {'my': 'dict'}
        mocked_request = mock.patch('requests.request', return_value=result)
        mocked_request.start()
        # request_url(...) will return static data 
        self.assertEqual(result, request_url('test', 'url'))
        self.assertEqual(result, request_url('test', 'url'))
        self.assertEqual(result, request_url('test', 'url'))

        mocked_request.stop()

So, you need just describe expected data. Also you can mock API using httpretty.

Hope this helps.

like image 194
Danila Ganchar Avatar answered Oct 20 '22 01:10

Danila Ganchar