11

In my python code, I am expecting exceptions could possibly be raised after calling method requests.Session.request(), for example these:

  • requests.exceptions.ConnectTimeout
  • requests.exceptions.ReadTimeout
  • requests.exceptions.Timeout

When any of these expected exceptions are raised, I handle them appropriately, for example possibly a retry situation.

My question, I am using py.test for unit testing, and I purposely want to inject raising exceptions from specific parts of my code. For example, the function that calls requests.Session.request(), instead of returning a valid requests.Response, it raises a requests.exception.

What I want to make sure that my code successfully handles expected and unexpected exceptions coming from other packages, which include those exceptions from requests.

Maybe... Is there a @decorator that I could add to the aforementioned function to raise exceptions upon request during unit testing?

Suggestions for doing exceptions injections for unit testing? (proper phrasing of my question would be greatly appreciated.)

Thanks for the responses!!!

Here is the entire singleton class that creates requests.Session and calls requests.Session.request():

class MyRequest(metaclass=Singleton): def __init__(self, retry_tries=3, retry_backoff=0.1, retry_codes=None): self.session = requests.session() if retry_codes is None: retry_codes = set(REQUEST_RETRY_HTTP_STATUS_CODES) self.session.mount( 'http', HTTPAdapter( max_retries=Retry( total=retry_tries, backoff_factor=retry_backoff, status_forcelist=retry_codes, ), ), ) def request(self, request_method, request_url, **kwargs): try: return self.session.request(method=request_method, url=request_url, **kwargs) except Exception as ex: log.warning( "Session Request: Failed: {}".format(get_exception_message(ex)), extra={ 'request_method': request_method, 'request_url': request_url } ) raise 
4
  • 1
    Could you show your code under test? In particular, the way session is initiated and used. Thanks. Commented Dec 24, 2016 at 16:28
  • 3
    You could inject a fake object which creates the requests, rather than hard coding requests.Session. Can you create a small example which we can discuss? As small as possible (maybe 5-10 lines). See how to create a minimal reproducible example Commented Dec 24, 2016 at 16:29
  • Just added to this question the class instance method that its only purpose is calling requests.Session.request(). Is this helpful? Commented Dec 24, 2016 at 16:52
  • How do you inject fake objects into your code during a py.test? Commented Dec 24, 2016 at 17:30

3 Answers 3

9

In my application I am catching exception requests.exceptions.ConnectionError and returning message which is in expected variable below. So the test looks like this:

import pytest import requests expected = {'error': 'cant connect to given url'} class MockConnectionError: def __init__(self, *args, **kwargs): raise requests.exceptions.ConnectionError def test_project_method(monkeypatch): monkeypatch.setattr("requests.get", MockConnectionError) response = project_method('http://some.url.com/') assert response == expected 
Sign up to request clarification or add additional context in comments.

Comments

7

You can make use of py.test raises, check it here: http://doc.pytest.org/en/latest/assert.html#assertions-about-expected-exceptions

Taking into account your code you could do something along the lines of the following:

from requests.exceptions import ConnectTimeout, ReadTimeout, Timeout from unittest.mock import patch import pytest class TestRequestService: @patch('path_to_module.MyRequest') def test_custom_request(self, my_request_mock): my_request_mock.request.side_effect = ConnectTimeout with pytest.raises(ConnectTimeout): my_request_mock.request(Mock(), Mock()) 

Moreover, you could make use of pytest.parametrize(http://doc.pytest.org/en/latest/parametrize.html) as well:

from requests.exceptions import ConnectTimeout, ReadTimeout, Timeout from unittest.mock import patch import pytest class TestRequestService: @pytest.mark.parametrize("expected_exception", [ConnectTimeout, ReadTimeout, Timeout]) @patch('path_to_module.MyRequest') def test_custom_request(self, my_request_mock, expected_exception): my_request_mock.request.side_effect = expected_exception with pytest.raises(expected_exception): my_request_mock.request(Mock(), Mock()) 

Here you can find some more examples about parametrize: http://layer0.authentise.com/pytest-and-parametrization.html

Comments

3

Patching, mocking and dependecy-injection are techniques to inject fake objects. Patching is sometimes hard to do right, on the other hand dependency injection requires that have to change the code you want to test.

This is just a simple example how to use dependency-injection. First the code we want to test:

import requests ... def fetch_data(url, get=requests.get): return get(url).json() # this is how we use fetch_data in productive code: answer = fetch_data("www.google.com?" + term) 

And this is then the test:

import pytest def test_fetch(): def get_with_timeout(url): raise ConnectTimeout("message") with pytest.raises(ConnectTimeout) as e: # and now we inject the fake get method: fetch_data("https://google.com", get=get_with_timeout) assert e.value == "message" 

In your example above, the mocking technique would be as follows:

def test_exception(): class TimeoutSessionMock: def get(self, *args, **kwargs): raise ConnectTimeout("message") mr = MyRequest() mr.session = TimeoutSessionMock() with pytest.raises(ConnectTimeout) as e: mr.request("get", "http://google.com") assert e.value == "message" 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.