7
\$\begingroup\$

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'] = (
            '<https://api.github.com/search/repositories?'
            'q=language%3Apython&sort=stars&page=1>; 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.

\$\endgroup\$
2
\$\begingroup\$

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.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.