15

I've recently started using pytest, and even more recently started using mock for mocking the requests library. I have made a requests.Response object okay, and for a 200 status code it works fine. What I'm trying to do here, is to use raise_for_status() to check for a rate limit exceeded error, and test that it handles the exception with pytest.

I'm using the Mock side_effect option, which seems to fire the exception I'm hoping, but pytest doesn't seem to recognise this as having happened and fails the test.

Any thoughts? I'm sure it's something obvious I'm missing!

The code I have for the class is:

class APIClient:
    def get_records(self, url):
        try:
            r = requests.get(url)
            r.raise_for_status()
            return r.json()
        except requests.HTTPError as e:
            print("Handling the exception")

In the test class, I have got:

@pytest.fixture
def http_error_response(rate_limit_json):
    mock_response = mock.Mock()
    mock_response.json.return_value = rate_limit_json
    mock_response.status_code = 429

    mock_response.raise_for_status.side_effect = requests.exceptions.HTTPError

    return mock_response


class TestRecovery(object):

    @mock.patch('requests.get')
    def test_throws_exception_for_rate_limit_error\
    (self, mock_get, api_query_object, http_error_response):

        mock_get.return_value = http_error_response
        print(http_error_response.raise_for_status.side_effect)

        url = api_query_object.get_next_url()

        with pytest.raises(requests.exceptions.HTTPError):
            api_query_object.get_records(url)

The output I get is:

    with pytest.raises(requests.exceptions.HTTPError):
>           api_query_object.get_records(url)
E           Failed: DID NOT RAISE

---------------------- Captured stdout call ---------------------- 
<class 'requests.exceptions.HTTPError'>
Handling the exception
huwf
  • 153
  • 1
  • 1
  • 5
  • 6
    Your test expects the method to raise an error, but it explicitly captures the error and prints a message instead. Either the test is wrong, or the code is. You could check that `raise_for_status` actually gets called, instead, then that `print` is called as a result. Alternatively (and preferably, in my opinion), let the method actually raise the error, then handle it in the caller. – jonrsharpe Feb 20 '16 at 12:48
  • 1
    For the unexpected test result, see @jonrsharpe's comment. As a general note: While [`unittest.mock`](https://docs.python.org/3/library/unittest.mock.html) is great for general mocking, faking and stubbing, for `requests` specifically, [`requests-mock`](https://pypi.python.org/pypi/requests-mock) can be more intuitive. – das-g Feb 20 '16 at 13:09
  • @jonrsharpe Thank you! That was the problem, I guess I was trying to do two things at once - check the exception happened and that it was handled. I also think I should probably change the flow and handle in the caller, although I'll have to check how to e.g. retrying for server unavailable errors. For testing exception handling generally, I guess I should put a handling function in the `except` block and check that gets called? – huwf Feb 20 '16 at 13:49
  • @das-g Good link! Looks a lot easier :) – huwf Feb 20 '16 at 13:53
  • Not if you don't need an error handling function; again I'd just let the error get thrown and handle it outside, where you can decide if you want to call again, ignore it or let it bubble further up the call chain. – jonrsharpe Feb 20 '16 at 13:56
  • 3
    @huwf Stack Exchange has always explicitly encouraged users to [answer their own questions](https://stackoverflow.com/help/self-answer). You can do that now that your problem is solved – xverges Sep 20 '18 at 21:13

1 Answers1

2

You are instructing pytest to expect an exception that should be raised in APIClient.get_records but inside that method definition you are already capturing the exception and just doing a print.

The exception is actually happening and it proved by seeing the result of your print in the console output.

Instead of that you should either check with the mock that the method raise_for_status was called.

lucrib
  • 448
  • 1
  • 9
  • 21