enderland
enderland

Reputation: 14145

How to use requests_mock to ensure the right session is being invoked?

I have a situation that is similar to this:

import unittest
import requests_mock
import requests

import mock

class ClassUnderTest:
    def __init__(self):
        self.session = requests.Session()
        self.foo_url = 'http://google.com'

    def do_foo(self):
        return self.session.get(self.foo_url)

    def do_foo_failing(self):
        # I want to prevent accidentally doing this sort of operation
        # instead of reusuing self.session
        return requests.get(self.foo_url)

I want to make sure that the methods perform correctly, but also ensure I am using the self.session object. In practice I have encountered this issue because I errantly added a requests.get call instead of re-usuing the same session.

However this means tests like this do not actually test this functionality:

class TestClassUnderTest(unittest.TestCase):

    @requests_mock.mock()
    def test_do_foo_should_pass(self, m):
        c = ClassUnderTest()

        m.get('http://google.com', status_code=200)
        r = c.do_foo()

        self.assertEqual(200, r.status_code)
        self.assertEqual(m.call_count, 1)

    @requests_mock.mock()
    def test_do_foo_should_fail(self, m):
        c = ClassUnderTest()
        m.get('http://google.com', status_code=200)
        r = c.do_foo_failing()

        self.assertEqual(200, r.status_code)
        self.assertEqual(m.call_count, 1)

I have thought about replacing self.session with a mock, but then I also have to do things like set status_code on the mock (and in my real code example I also need to do things like add a mock .json() method so that the code consuming the response functions correctly).

Is there a way using requests_mock to effectively guarantee that only self.session is being used (and not requests.get in this example)?

Upvotes: 1

Views: 568

Answers (1)

enderland
enderland

Reputation: 14145

The following works by wrapping all of the request.Session functions to a MockClass, with a wrapper that tracks usage of them:

class MockSession:
    def __init__(self):
        self.session = requests.Session()
        self._called_methods = {}

        def session_decorator(f):
            def func(*args, **kwargs):
                if f.__name__ not in self._called_methods:
                    self._called_methods[f.__name__] = 1
                else:
                    self._called_methods[f.__name__] += 1
                return f(*args, **kwargs)
            return func

        for name, f in inspect.getmembers(self.session):
            if inspect.ismethod(f):
            setattr(self, name, session_decorator(f))

After using this mock you can access the ._called_methods value to check how often individual methods are called. Note because of how requests_mock works you must do this at runtime (rather than loadtime by simply extending the requests.Session class with similar functionality).

Modifying the test code results in:

class TestClassUnderTest(unittest.TestCase):

    @requests_mock.mock()
    def test_do_foo_should_pass(self, m):
        c = ClassUnderTest()
        c.session = MockSession()
        m.get('http://google.com', status_code=200)
        r = c.do_foo()

        self.assertEqual(200, r.status_code)
        self.assertEqual(m.call_count, 1)
        self.assertEqual(c.session._called_count['GET'], 1)

    @requests_mock.mock()
    def test_do_foo_should_fail(self, m):
        c = ClassUnderTest()
        c.session = MockSession()
        m.get('http://google.com', status_code=200)
        r = c.do_foo_failing()

        self.assertEqual(200, r.status_code)
        self.assertEqual(m.call_count, 1) 

        # This will fail with an attribute error 
        # because that function was never invoked on the mock session
        self.assertEqual(c.session._called_count['GET'], 1)

Upvotes: 1

Related Questions