Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python Requests Mock doesn't catch Timeout exception

I wrote a unittest to test timeout with the requests package

my_module.py:

import requests

class MyException(Exception): pass

def my_method():
    try:
        r = requests.get(...)
    except requests.exceptions.Timeout:
        raise MyException()

Unittest:

from mock import patch
from unittest import TestCase
from requests.exceptions import Timeout

from my_module import MyException

@patch('my_module.requests')
class MyUnitTest(TestCase):
    def my_test(self, requests):
        def get(*args, **kwargs):
            raise Timeout()

        requests.get = get

        try:
            my_module.my_method(...)
        except MyException:
            return

        self.fail("No Timeout)

But when it runs, the try block in my_method never catches the requests.exceptions.Timeout

like image 269
Hank Avatar asked Mar 19 '16 00:03

Hank


1 Answers

There are two problems I see here. One that directly fixes your problem, and the second is a slight misuse of the Mocking framework that further simplifies your implementation.

First, to directly address your issue, based on how you are looking to test your assertion, what you are actually looking to do here:

requests.get = get

You should be using a side_effect here to help raise your exception. Per the documentation:

side_effect allows you to perform side effects, including raising an exception when a mock is called

With that in mind, all you really need to do is this:

requests.get.side_effect = get

That should get your exception to raise. However, chances are you might face this error:

TypeError: catching classes that do not inherit from BaseException is not allowed

This can be best explained by actually reading this great answer about why that is happening. With that answer, taking that suggestion to actually only mock out what you need will help fully resolve your issue. So, in the end, your code will actually look something like this, with the mocked get instead of mocked requests module:

class MyUnitTest(unittest.TestCase):

    @patch('my_module.requests.get')
    def test_my_test(self, m_get):
        def get(*args, **kwargs):
            raise Timeout()

        m_get.side_effect = get

        try:
            my_method()
        except MyException:
            return

You can now actually further simplify this by making better use of what is in unittest with assertRaises instead of the try/except. This will ultimately just assert that the exception was raised when the method is called. Furthermore, you do not need to create a new method that will raise a timeout, you can actually simply state that your mocked get will have a side_effect that raises an exception. So you can replace that entire def get with simply this:

m_get.side_effect = Timeout()

However, you can actually directly put this in to your patch decorator, so, now your final code will look like this:

class MyUnitTest(unittest.TestCase):

    @patch('my_module.requests.get', side_effect=Timeout())
    def test_my_test(self, m_get):    
        with self.assertRaises(MyException):
            my_method()

I hope this helps!

like image 77
idjaw Avatar answered Sep 28 '22 15:09

idjaw