Monkeypatching __builtin__ in tests for mocking I'm testing a little magic behaviour of my script that automatically reads from ~/.ghtoken to do automagic authentication on GitHub API requests. I want to test that this works properly by checking the headers in my mock requests to GitHub's API. I'm using HTTPretty to mock HTTP requests.

GitHub

def test_authentication(monkeypatch):
    import os.path
    import io
    import __builtin__

    auth_token = 'fhqwhgads\n'

    # Err... Just ignore how needlessly complicated this is.
    def monkeypatch_on_path_match(mod, name):
        original = getattr(mod, name)

        def patch(function):
            def patched(path, *args, **kwargs):
                if path == os.path.expanduser('~/.ghtoken'):
                    return function(path, *args, **kwargs)
                return original(path, *args, **kwargs)
            monkeypatch.setattr(mod, name, patched)
        return patch

    # Monkey-Patch exists() to say that the token exists.
    @monkeypatch_on_path_match(os.path, 'exists')
    def intercept_exists(path, *args, **kwargs):
        return True

    # Monkey-Patch open() to return specific file content.
    @monkeypatch_on_path_match(__builtin__, 'open')
    def intercept_open(path, *args, **kwargs):
        return io.BytesIO(auth_token)

    # Enable HTTPretty and register the index path.
    httpretty.enable()

    body = iter(mock_data.abbrev_search_bodies)

    def request_callback(request, uri, headers):
        headers['Content-Type'] = 'application/json; charset=utf-8'
        headers['X-RateLimit-Remaining'] = 10
        headers['Link'] = (
            '&lt;https://api.github.com/search/repositories?'
            'q=language%3Apython&amp;sort=stars&amp;page=1&gt;; rel="last"')
        return 200, headers, next(body)

    httpretty.register_uri(httpretty.GET,
                           "https://api.github.com/search/repositories",
                           body=request_callback)

    # Simply issue the request...
    ghdwn.get_github_list('java')
    assert httpretty.last_request().headers[
        'Authorization'] == 'token fhqwhgads'

    original_exists = os.path.exists

    # Now pretend that file DOES NOT exist!
    @monkeypatch_on_path_match(os.path, 'exists')
    def intercept_exists_fails(path, *args, **kwargs):
        return False

    @monkeypatch_on_path_match(__builtin__, 'open')
    def intercept_open_failure(path, *args, **kwargs):
        raise IOError('Could not find file!')

    # Issue the same request again:
    ghdwn.get_github_list('java')
    assert 'Authorization' not in httpretty.last_request().headers

    # Disable HTTPretty
    httpretty.disable()
    httpretty.reset()


My favourite part is the inexplicable monkeypatch_on_path_match decorator.

In the end, I feel like I did this because I could; not because it ends up testing whether or not my magic "authorize on the existence of a file" actually works.
I'm assuming you're fine with assert, generally I'd recommend using
the unittest
library.

Now for the monkey patching the other choice you have is to restructure
how the credentials are loaded.  At the moment the credentials are
loaded every time a request is performed - instead of that the
credentials could be loaded once, for the whole module as globals,
before invoking any other functionality.  Yes, globals are bad, but
unless you have another layer of indirection it'd be okay for this
purpose.

Done that way, you'd call e.g. ghdwn.load_credentials(), then issue
the call, then ghdwn.drop_credentials() and repeat.

If you stay with the current approach, I'd only move the patch method
out of the test into a proper function, as it's quite reusable, and give
it a better name maybe.

I'm not quite sure if there are other ways to open a file in Python,
regarding your concern whether you're actually testing the right thing;
it might also make sense to test two separate things: the function to
load credentials (from a custom path, so you can load from "/tmp/...")
and calling that function.  Then you wouldn't mock Python standard
functions, but instead only the credential loader, while you'd test the
loading of credentials with an actual test file.