Mock external API POST call in view from test view Python

Viewed 1917

I have an external API POST call that is being made from within my views.py as such:

class MyView(APIView):
  def post(self, request):
    my_headers = {
      "Content-Type": "application/json"
    }
    response = requests.post("https://some-external-api.com", data=json.dumps(request.data), headers=my_headers)

    return Response(status.response.status_code)

As you can see, it is a very simple case of making a POST call to the external API with the same data that is received to the views endpoint.

Right now, I am trying to create a unit test for this, while mocking the response from "https://some-external-api.com" so I obviously don't have to make an actual call to it every time this unit test runs. But I am having difficulty as I can't get the mock aspect to work, and everytime the request is sent to the actual external endpoint.

I know there are a lot of examples online, but nothing that I've tried seems to work. I've not seen examples whereby the mocked response should come from the view file itself. As of now, I have this:

@patch('requests.post')
def test_external_api_call(self, mock_post)
  mock_post.return_value.ok = True
  response = self.client.post(reverse('my-view'), {
    //my random dummy json object goes here
  }, format='json')

  self.assertEqual(response.status_code, 200)

As I mentioned, with the above code, there is an actual call being made to "https://some-external-api.com" rather than it being mocked.

1 Answers

No need to reinvent the wheel, just use the available mockers for the requests library such as requests_mock.

import json

import pytest
import requests
import requests_mock  # python3 -m pip install requests-mock


def post():
    my_headers = {"Content-Type": "application/json"}
    my_data = {"some_key": "some_value"}

    response = requests.post("https://some-external-api.com", data=json.dumps(my_data), headers=my_headers)

    print(response.status_code, response.json())


@pytest.fixture
def mock_post():
    with requests_mock.Mocker() as requests_mocker:
        def match_data(request):
            """
            This is just optional. Remove if not needed. This will check if the request contains the expected body.
            """
            return request.json() == {"some_key": "some_value"}

        requests_mocker.post(
            "https://some-external-api.com",  # Match the target URL.
            additional_matcher=match_data,  # Optional. If you want to match the request body too.
            status_code=200,  # The status code of the response.
            json={"the_result": "was successful!"},  # Optional. The value when .json() is called on the response.
        )

        yield


def test_requests(mock_post):
    post()
$ pytest -q -rP
================================================================================================= PASSES ==================================================================================================
______________________________________________________________________________________________ test_requests ______________________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
200 {'the_result': 'was successful!'}
1 passed in 0.04s
Related