Metadata-Version: 1.1
Name: automock
Version: 1.2.1
Summary: Utility to allow some functions to be 'mocked by default' when running tests.
Home-page: https://github.com/depop/python-automock
Author: Depop
Author-email: dev@depop.com
License: Apache 2.0
Description: automock
        ========
        
        |PyPI Version| |Build Status|
        
        .. |PyPI Version| image:: http://img.shields.io/pypi/v/automock.svg?style=flat
           :target: https://pypi.python.org/pypi/automock/
           :alt: Latest PyPI version
        
        .. |Build Status| image:: https://circleci.com/gh/depop/python-automock.svg?style=shield&circle-token=cbe5583fec309912d76bfc8b0321f6cfa23b7f6d
            :alt: Build Status
        
        There are some things that need to be mocked in unit tests.
        
        For example: API clients for other backend services - we don't want to run an
        instance of the other service just for our unit tests. The other service will
        have its own tests and we only want to test that our code confiorms to the API
        contract of the other service. Similarly for 3rd party services - we don't want
        our unit tests to connect out over the internet to talk to the 3rd party service
        (even if they offer a 'sandbox' test environment) for the same reasons as above,
        and because this is a recipe for flaky tests.
        
        (There is certainly a role for integration tests which do make live calls to
        other services, but the bulk of tests won't be this kind and need mocking).
        
        Python has the excellent `mock <http://www.voidspace.org.uk/python/mock/>`_ library to help with this.
        
        However, say you have six API clients for backend services which are used
        extensively in many places in code for your mobile app backend. You're going to
        end up with a big 'stack' of patch decorators on many tests, e.g.:
        
        .. code:: python
        
            @mock.patch('services.users.client.get_user', return_value=MockUser(id=1))
            @mock.patch('services.products.client.get_product', return_value=MockProduct(id=1))
            @mock.patch('services.paypal.client.make_payment', return_value=PaypalResult('success'))
            def test_some_web_view(self, *mocks)
        
        Say you have thousands of unit tests, these decorators need applying to many of
        them. Every time you write a new test you'll need to remember to patch things.
        
        Enter ``automock``.
        
        Basically we want some functions to be 'mocked by default' when running tests.
        But we also need to be able to easily replace the default mocks in some cases,
        where the test needs a specific return value. ``automock`` makes this easy-ish.
        
        
        Usage
        -----
        
        .. code:: bash
        
            pip install automock
        
        
        Introduction
        ~~~~~~~~~~~~
        
        The key idea is that we define a 'mock factory' for each function we want to be
        automocked. When called without arguments the factory should return a suitable
        'default' mock that will allow most tests to pass. The default mock factory is
        just ``MagicMock`` from the ``mock`` library.
        
        Registering a function to be mocked is simple:
        
        .. code:: python
        
            import automock
        
            automock.register('services.users.client.get_user')
        
        By default this provides a ``MagicMock`` and is equivalent to decorating *all*
        your test cases with:
        
        .. code:: python
        
            @mock.patch('services.users.client.get_user')
        
        (for more scenarios see `Customising mock factories`_ below)
        
        For this to work you just need to do two things.
        
        #. You need to ensure that the modules containing ``automock.register``
           calls get imported before the tests run. To achieve this we have an
           ``AUTOMOCK_REGISTRATION_IMPORTS`` config setting. This should contain string paths
           to modules containing registration calls, e.g.:
        
           .. code:: python
        
                AUTOMOCK_REGISTRATION_IMPORTS = (
                    'services.users.test_mocks',
                    'services.products.test_mocks',
                    'services.paypal.test_mocks',
                )
        
        #. If you're running your tests under `pytest <https://docs.pytest.org/en/latest/>`_
           then you don't need to do anything else - Automock registers a pytest plugin
           (named ``automock`` in pytest) that ensures your test cases all run patched.
        
        #. If you're running under another test-runner then your test cases need to inherit
           from one of our helper classes, e.g.:
        
           .. code:: python
        
                from automock import AutomockTestCase, AutomockTestCaseMixin
        
        
                class TestWebViews(AutomockTestCase):
                    ...
        
        
                class TestSpecialViews(AutomockTestCaseMixin, MyCustomTestCase):
                    ...
        
           This will ensure the mock patches get applied before the tests run, and stopped
           afterwards.
        
           Alternatively you can start/stop patching manually:
        
           .. code:: python
        
                from unittest import TestCase
        
                import automock
        
        
                class TestStuff(TestCase):
        
                    # as a decorator
                    @automock.activate()
                    def test_stuff(self):
                        # automocks active
                        ...
        
                    # as a context-manager
                    def test_other_stuff(self):
                        # automocks inactive
                        ...
                        with automock.activate():
                            # automocks active
                            ...
        
                        # automocks inactive
        
        
        Configuration
        ~~~~~~~~~~~~~
        
        Settings are intended to be configured primarily via a python file, such
        as your existing Django ``settings.py``. To bootstrap this, there are a couple
        of **env vars** to control how config is loaded:
        
        -  ``AUTOMOCK_APP_CONFIG``
           should be an import path to a python module, for example:
           ``AUTOMOCK_APP_CONFIG=django.conf.settings``
        -  ``AUTOMOCK_CONFIG_NAMESPACE``
           Sets the prefix used for loading further config values from env and
           config file. Defaults to ``AUTOMOCK``.
        
        The following config keys are available (and are prefixed with
        ``AUTOMOCK_`` by default, see ``AUTOMOCK_CONFIG_NAMESPACE`` above):
        
        -  ``<namespace>_REGISTRATION_IMPORTS`` list of import paths to modules
           containing ``automock.register`` calls
        
        
        Patching and imports
        ~~~~~~~~~~~~~~~~~~~~
        
        An **important point to note** about the path you mock:
        
        This has the same caveats as when using ``mock.patch`` directly. Namely that
        you must patch the path *where it is imported*.
        
        For example if you do:
        
        .. code:: python
        
            # mypackage/mymodule.py
        
            from services.product.client import get_product
        
        When you patch it:
        
        .. code:: python
        
            # won't work:
            patch('services.product.client.get_product')
        
            # works:
            patch('mypackage.mymodule.get_product')
        
        DON'T DO THIS (see this
        `blog post <http://bhfsteve.blogspot.co.uk/2012/06/patching-tip-using-mocks-in-python-unit.html>`_
        for more details).
        
        This import style will cause us problems if we want to mock-by-default all
        usages of a particular function, because we only register a single path to mock.
        
        Instead you need to use one of the following import styles *everywhere* in your
        codebase that the function to mocked is used:
        
        .. code:: python
        
            # mypackage/mymodule.py
        
            # either
            from services.product import client as product_client
            product_client.get_product(*args)
        
            # or
            import services.product.client as product_client
            product_client.get_product(*args)
        
        This will ensure that we can:
        
        .. code:: python
        
            automock.register('services.product.client.get_product')
        
        and have that work reliably.
        
        **NOTE:**
        
        Always ``import automock`` and use as ``automock.register`` to ensure there is
        only one registry active.
        
        
        Customising mock factories
        ~~~~~~~~~~~~~~~~~~~~~~~~~~
        
        It's likely you need to do more than provide a bare ``MagicMock``. For example
        we might want to customise the response based on some values from the request.
        
        In ``mock.Mock`` this is achieved via a 'side effect'. So we might want to
        define our mock factory like this:
        
        .. code:: python
        
            def batch_counters_mock(return_value=None, side_effect=None, *args, **kwargs):
                if return_value is None and side_effect is None:
                    def side_effect(product_ids, *args, **kwargs):
                        return {str(p_id): 0 for p_id in product_ids}
                return mock.MagicMock(return_value=return_value, side_effect=side_effect, *args, **kwargs)
        
            automock.register('services.products.client.batch_counters', batch_counters_mock)
        
        Note that we passed the custom mock factory as second argument to ``register``.
        
        As an alternative we can use decorator syntax:
        
        .. code:: python
        
            @automock.register('services.products.client.batch_counters')
            def batch_counters_mock(return_value=None, side_effect=None, *args, **kwargs):
                if return_value is None and side_effect is None:
                    def side_effect(product_ids, *args, **kwargs):
                        return {str(p_id): 0 for p_id in product_ids}
                return mock.MagicMock(return_value=return_value, side_effect=side_effect, *args, **kwargs)
        
        Now in our tests we can:
        
        .. code:: python
        
            import services.products.client as products_client
        
            def test_counters():
                counters = products_client.batch_counters([1, 2])
                # we got a default value for each of the ids we passed in:
                assert counters == {'1': 0, '2': 0}
        
        (This is a useless test of course, it's just to demonstrate the mocking)
        
        Okay. What if we need a custom return value for a particular test?
        
        Well, firstly the regular ``mock.patch`` still works, you could apply that in
        your test case.
        
        Automock also provides a ``swap_mock`` helper that allows us to take advantage
        of our custom mock factory.
        
        Let's say our factory looks like:
        
        .. code:: python
        
            @automock.register('services.things.client.do_something')
            def do_something_mock(success=True):
                if success:
                    return mock.MagicMock(return_value='OK')
                else:
                    return mock.MagicMock(side_effect=requests.HTTPError())
        
        In our tests we can:
        
        .. code:: python
        
            import pytest
            import requests
            from automock import swap_mock
        
            import services.things.client as things_client
        
            def test_success():
                # default mock from factory gives success response
                assert things_client.do_something() == 'OK'
        
            @swap_mock('services.things.client.do_something', success=False)
            def test_fail():
                # swap mock applies a customised mock from our factory
                with pytest.raises(requests.HTPPError):
                    things_client.do_something()
        
        What happened here is that the ``*args, **kwargs`` from our ``swap_mock`` call
        are passed through to the ``do_something_mock`` to *get a new mock* which is
        then applied in place of the default.
        
        We can also use this as a context manager:
        
        .. code:: python
        
            import pytest
            import requests
            from automock import swap_mock
        
            import services.things.client as things_client
        
            def test_do_something():
                assert things_client.do_something() == 'OK'
        
                with swap_mock('services.things.client.do_something', success=False):
                    with pytest.raises(requests.HTPPError):
                        things_client.do_something()
        
                assert things_client.do_something() == 'OK'
        
        
        Checking mocked calls
        ~~~~~~~~~~~~~~~~~~~~~
        
        It's common in tests to want to check if a mocked function was called, and
        with correct arguments etc. If you use ``mock.patch`` directly this is easy
        because it returns the mock object to you.
        
        Automock provides the ``get_mock`` helper to achieve the same thing:
        
        .. code:: python
        
            from automock import get_mock
        
            import services.things.client as things_client
        
            def test_success():
                assert things_client.do_something() == 'OK'
                mocked = get_mock('services.things.client.do_something')
                assert mocked.called
        
        
        Testing the automocked functions
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        
        Ok, so you've mocked your API clients or whatever. How do you test the mocked
        functions themselves if they're mocked out everywhere?
        
        Firstly, you could just not inherit from ``AutomockTestCase`` in those tests.
        
        But maybe you have a bunch of other automocks you want to keep in place still.
        
        Automock provides an ``unmock`` helper:
        
        .. code:: python
        
            import pytest
            import responses
            from automock import unmock
        
            import services.things.client as things_client
        
            @responses.activate
            @unmock('services.things.client.do_something')
            def test_do_something_not_found():
                responses.add(responses.GET, 'https://thingservice.ourcompany.com/api/1/something',
                              json={'error': 'Not Found'}, status=404)
                with pytest.raises(requests.HTPPError):
                    things_client.do_something()
        
        (for functions which make HTTP calls we recommend the excellent
        `responses <https://github.com/getsentry/responses>`_ library)
        
        Here we have un-mocked our client method so that we can test that it correctly
        handles a 404 response from the remote service.
        
        
        Compatibility
        -------------
        
        This project is tested against:
        
        =========== ===
        Python 2.7   * 
        Python 3.6   * 
        Python 3.7   * 
        Python 3.8   * 
        =========== ===
        
        Running the tests
        -----------------
        
        CircleCI
        ~~~~~~~~
        
        | The easiest way to test the full version matrix is to install the
          CircleCI command line app:
        | https://circleci.com/docs/2.0/local-jobs/
        | (requires Docker)
        
        The cli does not support 'workflows' at the moment so you have to run
        the two Python version jobs separately:
        
        .. code:: bash
        
            circleci build --job python-2.7
        
        .. code:: bash
        
            circleci build --job python-3.6
        
        py.test (single python version)
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        
        It's also possible to run the tests locally, allowing for debugging of
        errors that occur.
        
        Now decide which Python version you want to test and create a virtualenv:
        
        .. code:: bash
        
            pyenv virtualenv 3.6.4 automock
            pip install -r requirements-test.txt
        
        Now we can run the tests:
        
        .. code:: bash
        
            make test
        
Platform: UNKNOWN
Classifier: Environment :: Plugins
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 2.7
Classifier: Framework :: Pytest
