Product Manager & Software Engineer, Canadian transplant to Seattle & San Francisco (with a brief stint in Austin).
Django’s out of the box test client works great for testing simple views. You can pass it a url, execute a view, and check the returned response for what you expected.
However, sometimes you need to test multiple views that depend on something changing in the request, or even spoofing/mocking an external call. I’ll show you how to do both here.
We use this successfully at Movity to test multi-view OAuth scenarios that depend on funky handshakes to work properly.
Let’s take a simple test case:
from django.test import TestCase class YourFancyTest(TestCase): def setUp(self): pass def tearDown(self): pass def test_your_tricky_view(self): response = self.client.get('/something/') self.assertEqual(response.status_code, 200)
So far so good. But what if you want to change something about the request, e.g. passing in a different user? To solve this we’ll use this RequestFactory snippet.
RequestFactory (adapted from Simon Willison’s snippet) lets you simulate a request that can be passed into a raw view function. This won’t test your urlconf, though. Let’s see what it looks like.
from django.test import TestCase from yourproject.yourutils import RequestFactory # I assume you put RequestFactory in this module from yourproject.yourapp.views import something_view class YourFancyTest(TestCase): def setUp(self): pass def tearDown(self): pass def test_your_tricky_view(self): # Create the new request rf = RequestFactory() request = rf.get('/something/') # Get a specific user and log that user in (or change the user's name, or whatever) user = User.objects.get(id=1) login(request, user) # Call the view directly with the new request response = something_view(request) self.assertEqual(response.status_code, 200)
Ok, so we can modify the request object. What if you have a view that makes an external call (e.g. to an API) and the view depends on a successful response?
Ideally, your tests are self-contained and aren’t calling external services. The problem is, you don’t want to change the view itself to do something different for a test case.
So we have to find a way to inject a response at the right time to the API call and have the view think that it came from the service.
Fortunately, you use the excellent Mocker tool to help you with this. The way it works is that you create a mock request and pass that into the view you’re testing. You can instruct the mock to return the right answer at the right time.
Mocker uses a record-replay model. You specify actions for an object, then supply the fake data you want to inject. When you replay, calling the same action returns the fake data.
To change the request object, we’ll use the .proxy() functionality, which means we’ll mask an object and only replace a few key function calls.
You can also replace a whole function call from any module, which we’ll do using .replace().
from mocker import Mocker from django.test import TestCase from yourproject.yourutils import RequestFactory # I assume you put RequestFactory in this module from yourproject.yourapp.views import something_view class YourFancyTest(TestCase): def setUp(self): self.mocker = Mocker() def tearDown(self): pass def test_your_tricky_view(self): # Create the new (real) request rf = RequestFactory() request = rf.get('/something/') # Get a specific user and log that user in (or change the user's name, or whatever) user = User.objects.get(id=1) login(request, user) # Create the mock object. Proxy means we'll use the original object but we'll replace a few functions on it. # mock_request will look like a Request object for most purposes. mock_request = self.mocker.proxy(request) # Record: Let's replace a session variable. mock_request.session['your_session_variable'] # This will 'inject' the data 'Mocked!' for when the view calls request.session['your_session_variable'] self.mocker.result('Mocked!') # Record: Let's replace a function call. Assume it has 3 parameters. mock_fn1 = self.mocker.replace('yourproject.yourapp.utils.get_status_count') mock_fn1(ANY, ANY, ANY) # Tell the mock that get_status_count takes 3 params, and to match any call to it. # This will inject 10 as the status call result, regardless of what the function would have returned. self.mocker.result(10) # Important: this internally calls self.mocker.replay(), which enables the mock objects to return data. # It also cleans up a few things after the with exits. You'll need Python 2.6+ or have to future import 'with'. with self.mocker: # Call the view directly with the mock request. response = something_view(mock_request) self.assertEqual(response.status_code, 200) # Clear any existing mocks, e.g. the above proxy/replace. This is important if you're calling multiple views in the same test. self.mocker.reset()
Now, when the view runs and tries to get the session variable and the function call, it will use the mocked values instead of calling the function.
It will only do this once for each though – if you have multiple calls you’ll have to change the count within the mocker.
Note that if you only need to override a function call, you don’t need to create a mock request object and pass it into the view.
You should be able to use .replace() to get at the function that you need. You still have to use with self.mocker though.
To mock an external api call, just replace e.g. httplib2.Http with your mocked function and return whatever you want in the mock.
Let me know if you find this post helpful. 🙂
Posted in Tech on April 21, 2009
At SWSF09, I helped build http://hubb.me with team of 7 people over the weekend. The site lets you share multiple links in a single short URL. The sites are navigated by tabs at the top of the browser.
One great use of hubb.me is to combine your own online profiles, which for me aggregates my Facebook, LinkedIn, twitter, this blog and one of my travel blogs. (If you use Twitter try tweetree for now, there are a few issues with Twitter URLS inside a frame).
My personal Hubb: http://hubb.me/74148
If you’re interested in how we built hubb.me or the Startup Weekend experience, check out some of my teammates’ posts: Eric Wu; Danny Roa Part 1 Part 2; also, here’s a great summary of the event by Anand Iyer.
Posted in Uncategorized on April 1, 2009
A major factor in the credit crunch and subsequent economic downturn was a financial tool called a CDO, or ‘collateralized debt obligation’. If you’re not familiar, it was a profitable vehicle for banks to throw a bunch of mortgages into a pool and sell slices of that pool to outside investors.
Read on for a description of what a CDO is and how it screwed the economy.
How CDO credit ratings got messed up
Why banks they didn’t really know what they were doing
“Disastrously, it was just simple enough for untrained financial analysts to use, but too complex for them to properly understand. It appeared to allow them to definitively determine risk, effectively eliminating it. The result was an orgy of misspending that sent the U.S. banking system over a cliff.”
But by this point, the inertia in the banks might have been too big for this to have made a difference:
“Maybe he sensed the danger inherent in the system he’d help establish. By 2005, Li was among those warning about the limitations of his model. “The most dangerous part is when people believe everything coming out of (the model),” he told The Wall Street Journal.”
For a bit of sobering foresight, read this 2005 (!) article from the WSJ (somewhat long but very well written):
My favorite passage:
(just after a note on losses in the 2005 CDS market)
“The credit-derivatives market has since bounced back. Some say this shows that the proliferation of hedge funds and of complex derivatives has made markets more resilient, by spreading risk.
Others are less sanguine. “The events of spring 2005 might not be a true reflection of how these markets would function under stress,” says the annual report of the Bank for International Settlements, an organization that coordinates central banks’ efforts to ensure financial stability. To Stanford’s Mr. Duffie, “The question is, has the market adopted the model wholesale in a way that has overreached its appropriate use? I think it has.”