HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythongitMinor

Monkeypatching __builtin__ in tests for mocking

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
mocking__builtin__testsmonkeypatchingfor

Problem

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'] = (
'; 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'

origin

Solution

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.

Context

StackExchange Code Review Q#70045, answer score: 2

Revisions (0)

No revisions yet.