Search code examples
pythonpython-requestsmockingpytestpython-unittest

Pytest mock.patch requests AttributeError: does not have the attribute 'json'


I'm trying to test an api mock call using from unittest.mock import patch. I keep getting an AttributError when I include .json() in my function's response return (i.e. return response.json()).

When I exclude the .json() in my return statement, the test passes. I'm pretty new to testing and I can't figure out how to get around this error. Does anyone have an idea of how to get around this by keeping the .json() in the return statment?

enter image description here

enter image description here

enter image description here

Here is my code for reference: test_pytest.py

from unittest.mock import patch
from src.foo_helper import get_foo_data

@patch('requests.get',
       return_value={
           "version": "v1",
           "greeting": "Aloha 👋"
       }
       )
def test_get_foo_data(mock_get, mock_json):

    print("Mock_get: ", mock_get())
    print("Mock_json: ", mock_json())
    print("Function: ", get_open_play_data)
    # print("Function call: ", get_foo_data('https://example.com',
    #                                             CONFIG['KEY'],
    #                                             CONFIG['PASSWORD']))

    result = get_foo_data(
        'route', 'key', 'password'
    )
    print("Result: ", result)
    assert mock_get() == result
    assert False

foo_helper.py

import requests

def get_foo_data(route, key, password) -> object:
    # route should be a string
    try:
        response = requests.get(
            route,
            auth=(
                key,
                password
            )
        )
        print("Response: ", response)
        return response.json()
    except requests.exceptions.RequestException as e:
        print(f"There was a problem with your request: '{e}'")


Solution

  • As @LuísMöllmann already pointed out, the patching is incorrect. The actual usage was:

    requests.get().json()
    

    But the patching was:

    requests.get.return_value = {some dict}
    

    This means that requests.get() will already return {some dict} which then fails when .json() is called.

    Solution 1

    The dictionary response must be mocked at requests.get.return_value.json.return_value and not just the requests.get.return_value:

    @patch('requests.get')
    def test_get_open_play_data(mock_get):
        mock_json = {
            "version": "v1",
            "greeting": "Aloha 👋"
        }
        mock_get.return_value.json.return_value = mock_json
    
        result = get_open_play_data(
            'route', 'key', 'password'
        )
        print("Result: ", result)
        assert mock_json == result
    

    Solution 2 (Recommended)

    Don't reinvent the wheel of mocking the requests module. Use a library such as requests_mock which does it easily for you.

    import requests_mock as requests_mock_lib
    
    
    def test_get_open_play_data_using_lib(requests_mock):
        mock_json = {
            "version": "v1",
            "greeting": "Aloha 👋"
        }
        requests_mock.get("http://route", json=mock_json)  # If you want the mock to be used on any URL, replace <"http://route"> with <requests_mock_lib.ANY>
    
        result = get_open_play_data(
            'http://route', 'key', 'password'
        )
        print("Result: ", result)
        assert mock_json == result