Now it is possible to mock not only a function but also a class:
  class TestClass(object)
    def test_func_a(self, m):
      m.register_uri('GET', 'http://test.com', text='data')
      ...
    def test_func_b(self, m)
      m.register_uri('GET', 'http://test.com', text='data')
      ...
This new behavior mimics behavior of `patch` from `mock` library.
Added docs for this new feature.
Closes-Bug: #1404805
Change-Id: I8303dc4bc682cf12ffe86e7712b5a1c54de83efb
		
	
		
			
				
	
	
		
			203 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			203 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
# Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
						|
# not use this file except in compliance with the License. You may obtain
 | 
						|
# a copy of the License at
 | 
						|
#
 | 
						|
#      http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
#
 | 
						|
# Unless required by applicable law or agreed to in writing, software
 | 
						|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
						|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
						|
# License for the specific language governing permissions and limitations
 | 
						|
# under the License.
 | 
						|
 | 
						|
import functools
 | 
						|
 | 
						|
import requests
 | 
						|
 | 
						|
from requests_mock import adapter
 | 
						|
from requests_mock import exceptions
 | 
						|
 | 
						|
DELETE = 'DELETE'
 | 
						|
GET = 'GET'
 | 
						|
HEAD = 'HEAD'
 | 
						|
OPTIONS = 'OPTIONS'
 | 
						|
PATCH = 'PATCH'
 | 
						|
POST = 'POST'
 | 
						|
PUT = 'PUT'
 | 
						|
 | 
						|
 | 
						|
class MockerCore(object):
 | 
						|
    """A wrapper around common mocking functions.
 | 
						|
 | 
						|
    Automate the process of mocking the requests library. This will keep the
 | 
						|
    same general options available and prevent repeating code.
 | 
						|
    """
 | 
						|
 | 
						|
    _PROXY_FUNCS = set(['last_request',
 | 
						|
                        'register_uri',
 | 
						|
                        'add_matcher',
 | 
						|
                        'request_history',
 | 
						|
                        'called',
 | 
						|
                        'call_count'])
 | 
						|
 | 
						|
    def __init__(self, **kwargs):
 | 
						|
        self._adapter = adapter.Adapter()
 | 
						|
        self._real_http = kwargs.pop('real_http', False)
 | 
						|
        self._real_send = None
 | 
						|
 | 
						|
        if kwargs:
 | 
						|
            raise TypeError('Unexpected Arguments: %s' % ', '.join(kwargs))
 | 
						|
 | 
						|
    def start(self):
 | 
						|
        """Start mocking requests.
 | 
						|
 | 
						|
        Install the adapter and the wrappers required to intercept requests.
 | 
						|
        """
 | 
						|
        if self._real_send:
 | 
						|
            raise RuntimeError('Mocker has already been started')
 | 
						|
 | 
						|
        self._real_send = requests.Session.send
 | 
						|
 | 
						|
        def _fake_get_adapter(session, url):
 | 
						|
            return self._adapter
 | 
						|
 | 
						|
        def _fake_send(session, request, **kwargs):
 | 
						|
            real_get_adapter = requests.Session.get_adapter
 | 
						|
            requests.Session.get_adapter = _fake_get_adapter
 | 
						|
 | 
						|
            try:
 | 
						|
                return self._real_send(session, request, **kwargs)
 | 
						|
            except exceptions.NoMockAddress:
 | 
						|
                if not self._real_http:
 | 
						|
                    raise
 | 
						|
            finally:
 | 
						|
                requests.Session.get_adapter = real_get_adapter
 | 
						|
 | 
						|
            return self._real_send(session, request, **kwargs)
 | 
						|
 | 
						|
        requests.Session.send = _fake_send
 | 
						|
 | 
						|
    def stop(self):
 | 
						|
        """Stop mocking requests.
 | 
						|
 | 
						|
        This should have no impact if mocking has not been started.
 | 
						|
        """
 | 
						|
        if self._real_send:
 | 
						|
            requests.Session.send = self._real_send
 | 
						|
            self._real_send = None
 | 
						|
 | 
						|
    def __getattr__(self, name):
 | 
						|
        if name in self._PROXY_FUNCS:
 | 
						|
            try:
 | 
						|
                return getattr(self._adapter, name)
 | 
						|
            except AttributeError:
 | 
						|
                pass
 | 
						|
 | 
						|
        raise AttributeError(name)
 | 
						|
 | 
						|
    def request(self, *args, **kwargs):
 | 
						|
        return self.register_uri(*args, **kwargs)
 | 
						|
 | 
						|
    def get(self, *args, **kwargs):
 | 
						|
        return self.request(GET, *args, **kwargs)
 | 
						|
 | 
						|
    def options(self, *args, **kwargs):
 | 
						|
        return self.request(OPTIONS, *args, **kwargs)
 | 
						|
 | 
						|
    def head(self, *args, **kwargs):
 | 
						|
        return self.request(HEAD, *args, **kwargs)
 | 
						|
 | 
						|
    def post(self, *args, **kwargs):
 | 
						|
        return self.request(POST, *args, **kwargs)
 | 
						|
 | 
						|
    def put(self, *args, **kwargs):
 | 
						|
        return self.request(PUT, *args, **kwargs)
 | 
						|
 | 
						|
    def patch(self, *args, **kwargs):
 | 
						|
        return self.request(PATCH, *args, **kwargs)
 | 
						|
 | 
						|
    def delete(self, *args, **kwargs):
 | 
						|
        return self.request(DELETE, *args, **kwargs)
 | 
						|
 | 
						|
 | 
						|
class Mocker(MockerCore):
 | 
						|
    """The standard entry point for mock Adapter loading.
 | 
						|
    """
 | 
						|
 | 
						|
    #: Defines with what should method name begin to be patched
 | 
						|
    TEST_PREFIX = 'test'
 | 
						|
 | 
						|
    def __init__(self, **kwargs):
 | 
						|
        """Create a new mocker adapter.
 | 
						|
 | 
						|
        :param str kw: Pass the mock object through to the decorated function
 | 
						|
            as this named keyword argument, rather than a positional argument.
 | 
						|
        :param bool real_http: True to send the request to the real requested
 | 
						|
            uri if there is not a mock installed for it. Defaults to False.
 | 
						|
        """
 | 
						|
        self._kw = kwargs.pop('kw', None)
 | 
						|
        super(Mocker, self).__init__(**kwargs)
 | 
						|
 | 
						|
    def __enter__(self):
 | 
						|
        self.start()
 | 
						|
        return self
 | 
						|
 | 
						|
    def __exit__(self, type, value, traceback):
 | 
						|
        self.stop()
 | 
						|
 | 
						|
    def __call__(self, obj):
 | 
						|
        if isinstance(obj, type):
 | 
						|
            return self.decorate_class(obj)
 | 
						|
 | 
						|
        return self.decorate_callable(obj)
 | 
						|
 | 
						|
    def copy(self):
 | 
						|
        """Returns an exact copy of current mock
 | 
						|
        """
 | 
						|
        m = Mocker(
 | 
						|
            kw=self._kw,
 | 
						|
            real_http=self._real_http
 | 
						|
        )
 | 
						|
        return m
 | 
						|
 | 
						|
    def decorate_callable(self, func):
 | 
						|
        """Decorates a callable
 | 
						|
 | 
						|
        :param callable func: callable to decorate
 | 
						|
        """
 | 
						|
        @functools.wraps(func)
 | 
						|
        def inner(*args, **kwargs):
 | 
						|
            with self as m:
 | 
						|
                if self._kw:
 | 
						|
                    kwargs[self._kw] = m
 | 
						|
                else:
 | 
						|
                    args = list(args)
 | 
						|
                    args.append(m)
 | 
						|
 | 
						|
                return func(*args, **kwargs)
 | 
						|
 | 
						|
        return inner
 | 
						|
 | 
						|
    def decorate_class(self, klass):
 | 
						|
        """Decorates methods in a class with request_mock
 | 
						|
 | 
						|
        Method will be decorated only if it name begins with `TEST_PREFIX`
 | 
						|
 | 
						|
        :param object klass: class which methods will be decorated
 | 
						|
        """
 | 
						|
        for attr_name in dir(klass):
 | 
						|
            if not attr_name.startswith(self.TEST_PREFIX):
 | 
						|
                continue
 | 
						|
 | 
						|
            attr = getattr(klass, attr_name)
 | 
						|
            if not hasattr(attr, '__call__'):
 | 
						|
                continue
 | 
						|
 | 
						|
            m = self.copy()
 | 
						|
            setattr(klass, attr_name, m(attr))
 | 
						|
 | 
						|
        return klass
 | 
						|
 | 
						|
 | 
						|
mock = Mocker
 |