I'm using Django REST Framework for an API that I'm working on. For a couple of reasons, I would like to use class-based views. However, I'm a bit particular about my unit testing, and I never allow my unit tests to touch the database. Note: I always use the "trick" demonstrated by Carl Meyer at Pycon 2012 where he mocks out the Cursor wrapper.
cursor_wrapper = Mock()
cursor_wrapper.side_effect = RuntimeError("No touching the database!")
@patch('django.db.backends.util.CursorWrapper', cursor_wrapper)
class TestMyCode(TestCase):
Here is link if you are interested to the slide.
I have a method in one of the views that checks something in the database. To be DRY it is shared between a POST and a PUT. But, I'm having problems mocking it out for my unit test. That's because the classmethod as_view creates a new instance and class dispatch and returns the "handler" function that dispatch returns. So, I can't seem to get shared method in my class-based view to mock it.
I can mock out the models used by the class based view, but then I have to essentially break my goal of being "DRY" and copy the code in both the POST and PUT. I guess I could refactor the code and move the logic on to the Model. But, I'm not positive I want to do that.
How can you mock out a shared method of a class-based view to avoid actually touching the database? Just avoid them?
I think you answered your own question. The same things you use to test any web framework apply to Django, such as inversion of control and dependency injection. You can keep it pretty simple in Python, so don't be intimidated or turned-off by what exists in something like Spring for example.
Why don't you move the code out of the class-based view? Your code is still not going to be DRY if you for some reason need the same logic elsewhere. Just because it's Django doesn't mean good programming principles don't apply.
I would suggest just abstracting some things out in new classes/python modules such as services (as a concept, not Django's definition of services) and other logic abstractions for data access. Then you are independent completely of the request/response lifecycle of a Django view. There's a tendency of Django and Rails developers to put every bit of logic directly in either the model or the view. This just leads to god classes and things that are hard to test.
You can facilitate this as well by thinking of your view as a light abstraction that handles things like marshalling params (GET/POST) etc. to the rest of your code and calling the necessary logic encapsulated elsewhere. IMO, if you want testable code, 99% of the logic should be outside the web context unless it's absolutely crucial to the process. This makes it also easier to run things in the background and in parallel.
What you should end up with are normal python modules and classes that are easy to test because they don't have direct dependencies on HTTP. If you need to mock HTTP, you can simply mock the request object. You are fortunate in that the combination of python/django makes it easy to dump out and mock these things as simple dicts/kwargs.
One thing I realized using class-based views is they are good for using with mixins and enforcing some conventions (returning json, open graph properties, etc), but using the more "advanced" class based views that directly require models such as DetailView just complicate things unnecessarily. These views are nice for admin screens, but for real apps help more than hurt. They make things hard to test and murder performance unless you find a nice, seamless way to integrate caching layers and such. At this point, it's usually just to inherit from View or TemplateView and be done with it.
Regarding DB mocking specifically, create your mocks and go through your business logic. Then it won't matter what you are inputting/outputting as long as it conforms to a particular set of rules and interfaces. See something like Mixer for example. You can also simply create/destroy temp DBs during testing. One way is to create separate settings modules for dev/staging/production/testing, etc. and load them dynamically depending on the environment. That way you can avoid damaging your working dev database when you run unit tests. Of course this is crossing more into a form of integration testing, but you should probably be doing some of that as well. The aforementioned solutions are common in other ORMs such as Hibernate.
Related to the previous, you can do something like the code below in your settings to use an in-memory database for unit testing. Eventually though, you still need to consider integration testing against your actual data store type, for example MySQL
if 'test' in sys.argv:
DATABASES['default']['ENGINE'] = 'sqlite3'
;tldr
Put your logic outside class views into proper objects and modules.
Don't lock yourself into trying to make the various bundled class-based views work for real apps and every use case; roll your own.
Use generally good TDD principles such as IOC, passing required parameters to constructors, loosely couple things, avoid excessive proprietary state requirements (particularly HTTP).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With