Lightning Talks Day 1 by Kojo Idrissa
Published October 25, 2019
This video features Wayne Merry at DjangoCon US 2017 in Spokane, Washington, USA.
DjangoCon US 2017 - Practical Unit Testing in Django by Wayne Merry
This talk is an opportunity for you to explore practical ways of improving the test code you write. Unit testing can be challenging, but with the right toolbox of techniques, it is much easier to write unit tests that not only enable high degrees of code coverage, but assurance on each action of your code.
Django provides an excellent test environment that facilitates testing across the whole of a project, however Django’s documentation and many online examples focus on integration tests. Any typical use of the Django test client is an integration test. Tools such as selenium also provide a frame work for usability tests, functional tests or integration tests. What is missing in this is a close look at unit tests. It is difficult to obtain high code coverage with integration tests alone.
This talk will build on Daniel Davis’ DjangoCon2015 talk “Why Unit Testing Doesn’t Have To Be So Hard”. That talk introduced the concept of using mocking to deal with the complexity of unit testing and gave a number of simple examples. In this talk, we will apply mocking, dummy objects and harnesses to unit test in the Django environment.
We will focus first on class based views. Django provides an extensive Generic Class Base View hierarchy of base classes and mixins. These define a series of methods that focus on various elements of the response process. For more complex applications, this system provides much of what is needed but often customizations are needed and these can take the form of subclasses overriding one or more methods, or perhaps mixins that are built to implement abstractions of these customizations.
In order to unit test these customizations, we want to place each individual method under test. To obtain strong assurance of code performance, we want to place under test each action of the code, plus its coupling with its base class(es). A test harness, mocks and dummy objects all assist in this process and we will explore examples of such. Mocks particularly facilitate our tests by us being able assert on what is passed on other method calls and on the super() call. Mixins are used to implement customization abstractions. Their methods can be unit tested making use of dummy subclasses.
Form classes also benefit from unit testing. Form classes may define clean methods for validation, and these clean methods can be called directly in unit tests for both valid and invalid data. Some modelform classes may implement business logic in their save() methods and these also highly benefit from unit testing.
Both forms and views often make use of the ORM. When performing integration testing, this often means setting up test fixtures, but for unit testing it might be much more efficient to mock out ORM calls such as filter(), all(), count(), etc. Sometimes code under test will chain these ORM functions and this also can be mocked.
We will then consider a more complex example of a view that makes use of an inlineformset. inlineformsets are more complex form objects, but various approaches can be used to unit test views that make use of formsets (along with unit tests of the formset itself).
We will close with some template unit testing.
The content of this talk is built on examples taken from real systems implementation. These should give many Django practitioners a boost in their day to day testing toolkit.
This talk was presented at: https://2017.djangocon.us/talks/practical-unit-testing-in-django/
LINKS:
Follow DjangCon US 👇
https://twitter.com/djangocon
Follow DEFNA 👇
https://twitter.com/defnado
https://www.defna.org/
Wayne Merry argues that Django projects need focused unit tests alongside integration tests. A unit test should isolate one method, replace its dependencies with mocks or simple dummy objects, and verify both the method’s direct effects and how it calls collaborators; he demonstrates this approach with class-based views, forms, mixins, and template tags. Integration tests should cover user stories and the full Django stack, while unit tests efficiently cover branches and edge cases that are difficult to reproduce with fixtures. He acknowledges some maintenance when Django or application behavior changes, but says detailed unit tests usually expose problems in the code under test rather than creating unnecessary test maintenance.
Summarised automatically from the transcript.
Automatically transcribed, so expect mistakes in names and technical terms.
Speaker 1: Thank you for coming. Can you all hear me? No foldback here. So today we're going to look at practical unit testing in Django. If you went to the testing talk yesterday, we're going to dive into a bit more detail. specifically about unit testing rather than uh general uh Django testing. And so This uh uh if you're uh if you've not done mocking before, a good intro talk is uh from DjangoCon US 2015, why unit testing. doesn't have to be so hard. That's when uh my introduction to sort of to mocking happened then and then using that in a Django context and a few uh few uh tears later then we've sort of developed some techniques.
Speaker 1: So that's definitely a good talk to check out if you're new to this area. And so what we're covering is we're covering unit testing. We're going to look at some examples of testing with views, forms, fields, and some template tags. We're not covering integration testing, so I'm not going to get into the whether you should use or not. use mock uh mocks when it comes to those things that much apart from stating a personal opinion I never use mocks in integration tests if I can avoid it anyway uh but they're essential when it comes to unit test So why unit test? When I I started my Django adventure by doing the tutorial. And the tutorial has a section on testing. That section on testing is integration testing.
Speaker 1: Now the problem is as a project grows in scope, it becomes very hard to get a high level of code coverage. uh using the Django test client where the code that you're trying to test might be 20 method calls down the tree. And so that doesn't really work in terms of really exploring uh the code that you're pasting under test in a particular module. So we need we need a more direct way of attacking what we're trying to test rather than trying to test the whole stack. Like in a in a project you would do uh integration testing so those tests are still there but then you you So there's a there's a percentage of tests that are integration and functional tests, but a whole lot of tests are going to be unit tests.
Speaker 1: There's many dependencies that can exist in a project. Uh methods uh uh can often be more than just take what the uh parameters uh that come in and a result that comes out, there's side effects, there's there's classes, there's attributes of classes You might be changing those attributes of classes. And so those things are side effects and they they increase the complexity of testing. And so we need a way of getting some sanity about all this because that kind of drives you crazy. And sometimes really it can be so complicated complicated it can just not be even plausible to try and manage it at such a high level. So what is a unit test? The idea is to place one
Speaker 1: method under test. And you're testing the internal operation of that method. You're testing how it calls other methods. And you're looking at what it does when it gets the results of other methods back and what it might do with that, which might be simply just returning it onwards. Then you presume everything else is reliable. So Python's duck-based typing, so can you tell me a bit of interaction here? What is that animal that we see on the screen? Oh, we don't see anything on the screen at all now. Hopefully that stays there. Don't touch anything. Okay, well so the point is
Speaker 1: It doesn't really matter what is what is the big picture of what you're testing. You are testing a method. So we've got the nose of a duck, a little toy duck on this line. It doesn't matter the thing is a line, you're just testing that narrow bit with a nose, that red nose, and so long as that does, you only need to do enough to make it work in the context of your method Everything else that it does doesn't really matter. And I'll fill that out in some details as we go along. But to get you thinking, if you're testing some some code that takes an OOM result and you're say adding something to it, you actually don't need the RRM to test your what you are doing. The RRM itself is out of scope of your particular method.
Speaker 1: And we'll have a look at some examples. The point is if you're testing just at that red nose, that's all that matters. Everything else is irrelevant. So our first sort of domain that we want to talk about is testing class-based views. If you're using functional views, I I don't really use them anymore so I'm going to be focused on class-based views. Now class-based views, there's a big hierarchy and there's lots of methods in there. What then typically happens happens is you might be you might be uh inheriting from say create view or update view and you might be defining a few methods that add to that. Just do a few things extra, maybe just one line of code extra. What you're testing is that one line of code and the call to the super
Speaker 1: function and the result that comes back. And that's all you're testing. We're not testing anything else. So what we um so as the slide talks about each override that you make is going to be a method that you that you're you're either deriving from the class and writing a specific override or perhaps you've noticed you've done that a few times and so now it's time for a mix-in And so we're gonna we're going to uh define a mix-in that does one or two things in one or two methods and we want to test that. So what we want to do is we want to test those methods Now what we want what we don't want to do is we don't want to call the Django test client because that's an integration test and that relies on the whole kick and caboodle to be there.
Speaker 1: We don't really want that. So what we need to do is we need to basically create enough of our view in memory so we can call the function we actually want to test. And so the top function there is a function that just rips out a little bit of from the asView function that's uh from the Django class-based view hierarchy to set up a test object, a view object, I should say. So it it sets up the attributes for request argues and keywords and uh ever returns the view. If you look at as view in the Django source code, you'll notice that that exists in there. It does a few other things, but they're not really needed for our unit test. And then to actually test this thing In the setup function in the test class, we see that we go to the request factory.
Speaker 1: So that was the existence of the request factory as talked about yesterday. We in this case case we're testing a get method, but that could be that could be other HTTP methods. It doesn't really matter what URL you give it. You don't need to worry about URL resolving. You can call it ABC, right? It's really irrelevant, right? Just feed it some string. Right, we create our view, which is the view under test , and we call that method that's up the top. And that sets up the view, and then what we can do is we can directly call call the method. So if we're if our method that's if our class on a test has got say a dispatch method override, then we can just directly call that by going view. dispatch. with a request and any arguments, keyword arguments.
Speaker 1: So we can directly call that method and we don't need to worry about all everything else that goes with it. If we're common functions to override is get context data. You just call it directly with a uh with a dictionary as your keyword args and then you look at then you look at what it does. inside. So moving on to forms. So we want to test the the idea is to test every action of a method to get some kind of assurance that this code is going to do what it does. So here's a here's a a uh function we want to test. So it's got so it's the clean method of a form and what it does is it's testing two dates and if the end date is uh after the start date we just want to make it the same as like we want to make the start date the same as the end date.
Speaker 1: So if the start date's later. So does it do that or not? And if there's an exception, then we want that not we want it to basically do nothing. So here are two tests that do that. So if we have a look what they do, the first thing is they patch the cloud clean method that's in that's inherited. The supercall is going to call the form clean method. We actually don't want to call the the form clean method because we're not interested in testing what that does. Uh we just want we we so we need to patch that. And so the auto spec means it'll test whether we're calling it in the way that it would be expected. And then we give it a result value. We're just going to give it a result of ABC.
Speaker 1: We can call it whatever we want. And then so then the method, the first line is it creates the form. Form equals self-test form. Then it sets up some clean data. Now when you're doing an integration test, that's already done somewhere else. But when you when we're just going to be calling clean, so clean data doesn't exist, so we need to make that. That's a dependency So we need to set that up and we'll put some clean data in there. In the first one, there is no end date, so that would throw an exception. We're testing for only a start date. Then it says result equals form clean. It calls the clean method directly. Then it looks at what comes out. The first assertion is did it call the super method? Did that claim method of the form
Speaker 1: get called? And was it called once? Which is what we expect. The previous method has got one call to super. What happens if we made a coding error and we called it twice? Well then the assertion will fail. Then the next line is it asserts that clean data has only got a start date in it. There's no end date that appeared because our code's not supposed to do that. Now if I if I wanted to pass it, if I want to throw the exception and not capture it, I could use an assertion called assert raises and put the exception in there and then the test will pass if an exception is thrown that's not captured. So I can whether I need to throw the exception or not, I can test specifically for that.
Speaker 1: And then the final thing is this last assertion is is the result ABC? We go back and have a look at the code. It takes result, if the result from super is put into a variable called result, and then down the bottom, return result. If I leave that bottom line out, forget to return The result, then that final assertion will fail. So I am testing, it's my method that takes the result of the super call and will pass that out to the caller. I want to test that. That's something that my method is doing. So anything that my method's doing, I want to test that. And that's what that finds assertion. Assert result ABC needs to test. If I change the return value back on the patch at the top line to D
Speaker 1: E F then my assertion will fail unless I test for DEF. So that's the idea that's the concept of detailed unit testing So going on, let's just talking about this at a more conceptual level. So methods do things. And we want to try and capture each thing. that it does. So some of it is direct in method actions such as assignments or doing some sort of mathematics or whatever. And the other thing that they typically do is call stuff. And most often, but not always, that's a call to super. But sometimes it's a call to other things. Whatever it calls, you want to test that it's calling it as you expect it will call it, given the inputs that you'll feeding to this function.
Speaker 1: If you say if you only sometimes call super based on certain inputs, then you want test case that test for the domain of inputs that should call super and then for the ones that shouldn't call super you'll be looking that its call count is zero. Now as soon as you introduce an if statement you've got a code branch and so that's likely to mean more than one test case so you can test each branch If you're getting too many of them and you're getting a whole pile of test cases, that's a prime indication that you need to break the thing up. Just a few notes on in uh the Python mock library has this thing called call args and call args lists. So to save you a bit of uh blood, sweat, and tears, uh
Speaker 1: the first uh index element contains all the positional arguments. It's not just stuff that you put in star args but anything that's called as a positional argument. The second index is anything that you call as a keyword argument. If the thing is a method that's on an object, self will be the first positional argument. But if it is a class method Class is not passed as a positional argument, even though you put it in the code, because Python already knows the name of the class, so class is actually not passed as the first positional keyword, so first positional arg, even though you actually physically write CLS or whatever you want to call it in the code. And if it's a static method where you're expecting not nothing to be
Speaker 1: be passed at that stage. Call args list is used when you're calling the function more than once and it allows you to to make assertions on the parameters for each call. So next thing is mix-ins. So we discussed mix-ins a little bit before. And so if you write a mix-in And you you'll be wanting to test a meth its method. So in this case we've got a validation object which is design um It it's it's a mix-in, so it's designed to work with other with other stuff. Um and uh it has an init method that does some stuff. So to test that, uh in our test search Of a val mix in, which is not in your database, so we can use simple test case to make things a bit faster.
Speaker 1: So we create a dummy field that inherits from the test class that's the the class that's under test plus char field. That field doesn't need to do it, that class doesn't need to do anything else, so that's why we've got pass there. And when we create this field, we're going to be calling the init method. But we don't want to call the init method of char field, because I don't care what goes on on the in inside that I'm not testing Django, right? I'm testing my code. So I don't want to I I just I need to patch that thing. And so wherever the top the wherever serve our mixins defined that's the thing I net put next to patch object module where imported to uh not where it's defined
Speaker 1: where it's imported to the init method auto spec equals true init doesn't return anything so don't only return So then in the test I create the test field. You could see I'm using um keyword args, the double star, I'm passing in whatever. Doesn't matter what, I'm just passing in some dictionary But this init method is supposed to pass that on. This test case is testing where I provide no settings to the actual mix-in. But the mix-in is used in a context of where it's mixed in with something else. You never use it alone. And so one of the assertions which is the bottom one, test to see whether the init um method of the of the um
Speaker 1: base class was called with that eleven and twelve dictionary. That's what that bottom line is doing. So I'm actually seeing I'm testing the super call in the init method. And if it doesn't pass it on then my assertion will fail So just a few comments about mocks. By default, when you when you create a mock, everything's defined. So if you're testing that it's not there, you need to delete it. which is under um so in this um this test case uh here we see on line three and four there's del mock one dot save and del two mock save. This is testing something that uh uh is a mix in that uh could be used with uh
Speaker 1: a model form but it could be used in an ordinary form And model forms have got a save method and ordinary forms don't. Well what happens if I want to test it with something that has it and something that doesn't? And is it going to work or is it going to throw an exception? And so this um this uh test method tests for that. Um it's setting up a view that's got some forms, it creates a dictionary of forms Form one and form two and it then passes that to the form valid method of the thing that's under test. Another note is up the top you see there's three patches. It's patching the get success URL because that's what uh form
Speaker 1: val if you in your call form valid, call form valid calls the get success URL. So but I don't want it to do that. I don't want it to go through the class-based view hierarchy. I just want it to do what I I need this to do. thing to do. I don't want it to redirect. I don't it's calling the um it's it's doing it's saving several forms, it's doing it in a transaction. That transaction actually invoked, and the assertion that tests for that is the fourth up from the bottom. One little quick tip when it comes to these patches, when there's more than one of them The order you need it's like a mirror image. The closest one in is the first the TA patch. Then the next one up is the HR
Speaker 1: You need to do them in a mirror order, not the top patch is the first position or argument after self. You do them like in a in a mirror image is the best way that I found to think about it. about that. So we're getting low on time, so just a uh a quick um a quick look at uh template unit testing So here's a dictionary object that the base test just does what Django does, but it also does indirect lookups. So I test it to see whether it can do those things or not. So all I need to do is to set up a template with a minimal string that just loads the test tags and then actually invokes the tag. Set up a context, run it, which is what that render function does dumps the result in rendered
Speaker 1: and then I can assert that that rendered the tag is doing what it's supposed to be doing Alright, so in summary, a unit test is focused on a specific method, and that's it uh whatever that method's doing directly or what it's calling. Uh how it when I say when it what it's calling, only how it calls it and what comes back, not what's going on inside. That's effectively a black box. View classes, you set up the instance with sort of a dummy function and you'll probably find versions of that around on the net. or I'll I'll look to try and put these slides up. They've got the Twitter handle there, so I've got to get them hosted somewhere. And so you're testing on arguments to test the cohesion and you use
Speaker 1: dummy classes, subclasses to text to test mixing. Mocks , you can have act they have attributes which we didn't get too much time to go into. And you need to delete attributes and methods where you need to test for them
Speaker 2: Do you find that getting in particularly the last example where you're sort of a sort of mocking things and um getting into a little bit of the semantics of how the underlying layers work. Do you find that tests the semantics change between versions of Django or versions of Python? In other words, your tests are very detailed and very specific, and do you find that there is an additional maintenance burden? By getting into that much detail that you're that you're doing? I mean the tests look very nice, but I worry about maintenance.
Speaker 1: Look it it does exist, but it's minimal. And the advantage of testing this way is that it pays off for it so many times. What typically happens as you write all these unit tests is there's a lot of assurance on the underlying code when you come to do an integration test you might find that a few bits don't quite fit in quite right but once you fix the cohesion issues everything just seems to work. Going from one version to Django to the other, the problems that can arise from that are where you actually got to change the code. under test not the uh testing code because the testing code is mocking out all this other stuff but your code is expecting Django to work in a certain way way uh and that's all your testing.
Speaker 1: So the problems are really when they do arise are with your tested code, not your testing code and you want your tests to fail in that situation.
Speaker 3: So I have a question about um how do you go about deciding if an integration test would be better for a particular uh view rather than you know doing a unit test for each little block of code because it seems like Uh with an inter integration test you can easily request do the request and and make sure you get back what you're wanting uh but with a unit test In your examples anyway, there's a lot of setup in terms of mocking the things that you don't want and you have to decide what you don't want and and that type of thing.
Speaker 1: Um well the answer to that is I do both. Uh I integration test the user stories including the alternative cases from use case text so I want to see that those stories are captured in the integration tests and then the unit tests focus on the detail of it each function. So I I don't do a trade-off there, I actually do both, and it improves the integrity of the code. Once you get used to these mocks and dummy objects, you get used to pump them out pretty quickly like uh the ROM you can have complicated queries you don't need to worry about setting up fixtures you just look at your parameters um an integration test needs fixtures and the integration test will see if your query makes sense or not and then your unit test can deal with all the little edge cases where it'd be pretty much impossible to create all those fixtures.
Speaker 1: So in the end it ends up working Quite quickly.
Speaker 4: Um ,
As a project grows, testing only through the full stack makes it difficult to achieve coverage and directly exercise code buried many calls deep. Unit tests let you test individual methods directly while keeping integration and functional tests for broader behavior.
Discussed at 1:49It should focus on one method: its direct behavior, how it calls dependencies, and what it does with the results. The internals of those dependencies are treated as reliable and outside the unit’s scope.
Discussed at 3:23Create a minimal view instance in memory, set up a request with RequestFactory, and directly call the method under test. You only need to initialize the request, arguments, and keyword arguments that the method requires; URL resolution and the rest of the view stack are unnecessary.
Discussed at 6:28Patch the inherited clean method, provide the clean-data dependency manually, call the form’s clean method directly, and assert the relevant branches, super-call count, data changes, exceptions, and returned value.
Discussed at 8:47Define a small dummy subclass combining the mixin with the class it normally works with, then patch the base class behavior that is outside the test’s scope. Assertions can verify that the mixin passes arguments correctly to the super method.
Discussed at 14:10Render a minimal template that loads and invokes the tag, provide a test context, and assert that the rendered output is correct.
Discussed at 18:46There can be some maintenance, but the speaker says it is minimal and worthwhile. Version changes mainly require updates when the code under test depends on changed Django behavior; the mocked tests themselves are generally not the problem.
Discussed at 21:16Write both rather than choosing one. Integration tests cover user stories and alternative cases, while unit tests exercise each function’s details and edge cases without requiring extensive fixtures.
Discussed at 22:51Note: We understand that names change, people change, and bodies change. We respect each individual's journey and privacy. If you have any concerns about a video or need us to remove content, please don't hesitate to contact us. We will handle your request with care and promptly address any issues.
Published July 15, 2026
Published July 15, 2026
Published July 15, 2026
Published July 15, 2026
Published July 15, 2026
Published July 14, 2026