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

Question:

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}'")

Asked By: Kevin G

||

Answers:

Your mock returns a dictionary, not a response object like the original requests.get function:

@patch('requests.get',
       return_value={
           "version": "v1",
           "greeting": "Aloha  "
       }
       )

So basically, you’re doing:

{"version":"v1","greeting":"Aloha  "}.json()

Hence the error:

‘dict’ object does not have the attribute ‘json’

Answered By: Luís Möllmann

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