the_deuce
the_deuce

Reputation: 303

Mocking requests.post and requests.json decoder python

I'm creating a test suite for my module that uses the requests library quite a bit. However, I'm trying to mock several different return values for a specific request, and I'm having trouble doing so. Here is my code snippet that doesn't work:

class MyTests(unittest.TestCase):

    @patch('mypackage.mymodule.requests.post') 
    def test_change_nested_dict_function(self, mock_post):
        mock_post.return_value.status_code = 200
        mock_post.return_value.json = nested_dictionary
        modified_dict = mymodule.change_nested_dict()
        self.assertEqual(modified_dict['key1']['key2'][0]['key3'], 'replaced_value')

The function I am attempting to mock:

import requests

def change_nested_dict():
    uri = 'http://this_is_the_endpoint/I/am/hitting'
    payload = {'param1': 'foo', 'param2': 'bar'}
    r = requests.post(uri, params=payload)

    # This function checks to make sure the response is giving the 
    # correct status code, hence why I need to mock the status code above
    raise_error_if_bad_status_code(r)

    dict_to_be_changed = r.json()

    def _internal_fxn_to_change_nested_value(dict):
        ''' This goes through the dict and finds the correct key to change the value. 
            This is the actual function I am trying to test above'''
        return changed_dict


    modified_dict = _internal_fxn_to_change_nested_value(dict_to_be_changed)

    return modified_dict

I know a simple way of doing this would be to not have a nested function, but I am only showing you part of the entire function's code. Trust me, the nested function is necessary and I really do not want to change that part of it.

My issue is, I don't understand how to mock requests.post and then set the return value for both the status code and the internal json decoder. I also can't seem to find a way around this issue since I can't seem to patch the internal function either, which also would solve this problem. Does anyone have any suggestions/ideas? Thanks a bunch.

Upvotes: 19

Views: 41690

Answers (4)

Amos Baker
Amos Baker

Reputation: 839

An alternate approach is to just create an actual Response object and then do a configure_mock() on the original mock.

from requests import Response

class MyTests(unittest.TestCase):

    @patch('mypackage.mymodule.requests.post') 
    def test_change_nested_dict_function(self, mock_post):
        resp = Response()
        resp.status_code = 200
        resp.json = nested_dictionary
        mock_post.configure_mock(return_value=resp)
        ...

Upvotes: 1

SRC
SRC

Reputation: 2271

I bumped here and although I agree that possibly using special purpose libraries is a better solution, I ended up doing the following

from mock import patch, Mock

@patch('requests.post')
def test_something_awesome(mocked_post):
    mocked_post.return_value = Mock(status_code=201, json=lambda : {"data": {"id": "test"}})

This worked for me for both getting the status_code and the json() at the receiver end while doing the unit-test.

Wrote it here thinking that someone may find it helpful.

Upvotes: 52

Alex Pertsev
Alex Pertsev

Reputation: 948

Probably it is better for you to look at some specialized libraries for requests testing:

responses

requests-mock

requests-testing

They provide clean way to mock responses in unittests.

Upvotes: 2

match
match

Reputation: 11070

When you mock a class each child method is set up as a new MagicMock that in turn needs to be configured. So in this case you need to set the return_value for mock_post to bring the child attribute into being, and one to actually return something, i.e:

mock_post.return_value.status_code.return_value = 200
mock_post.return_value.json.return_value = nested_dictionary

You can see this by looking at the type of everything:

print(type(mock_post))
print(type(mock_post.json))

In both cases the type is <class 'unittest.mock.MagicMock'>

Upvotes: 19

Related Questions