Easy, Breezy, Beautiful... Django Unit Tests with Colleen Dunlap

This video features Colleen Dunlap at DjangoCon US 2025 in Chicago, Illinois, USA.

Easy, Breezy, Beautiful... Django Unit Tests with Colleen Dunlap
0:32:01
Published October 23, 2025
171 views

This talk was presented at: https://2025.djangocon.us/talks/easy-breezy-beautiful-django-unit-tests/

LINKS:
Follow Colleen Dunlap 👇
Website: https://medium.com/@colleen85052

Follow DjangoCon US 👇
https://fosstodon.org/@djangocon
https://x.com/djangocon

Follow DEFNA 👇
https://www.defna.org/

Video production by the presenter and DjangoCon US 2025 volunteers.

Summary

Unit tests check small, isolated pieces of code and give fast, repeatable feedback; behavior-focused tests verify inputs and outputs, while implementation-focused tests can catch issues such as N+1 database queries. Colleen Dunlap explains how mocks isolate dependencies and control test scenarios, and how Factory Boy, Django’s test case and setup_test_data, Django-specific assertions, decorators, and test-runner options make tests easier to write and run. She recommends testing deliberately, using implementation checks where useful, and working with Django’s testing tools rather than against them.

Key takeaways

  • Unit tests isolate small pieces of code to provide fast, reproducible feedback, while TDD uses tests to define requirements before implementation.
  • Behavior tests check expected results; implementation tests can verify details such as whether code makes an excessive number of database queries.
  • Mocks help isolate external dependencies, make unpredictable behavior repeatable, and direct tests through specific code paths.
  • Factory Boy factories and Django’s setup_test_data simplify reusable test data and relationships, while Django’s test case provides useful testing features.
  • Django offers assertions, decorators, and test-runner options for checking queries, adjusting settings, selecting tests, debugging, and running tests in parallel.

Summarised automatically from the transcript.

Chapters

  1. 0:00 Introduction Colleen Dunlap introduces herself and her background before turning to unit testing.
  2. 2:33 Unit Testing Fundamentals Defines unit tests, contrasts them with integration tests, and explains their benefits and connection to test-driven development.
  3. 5:48 Behavior and Implementation Testing Compares testing what code does with testing how it works, using Django query optimization as an example.
  4. 8:54 Mocking Dependencies Explains how mocks isolate dependencies, control test scenarios, and verify implementation details.
  5. 11:12 Test Setup and Factories Covers setup and teardown methods, test data creation, Factory Boy, and reusable related objects.
  6. 14:27 Django-Specific Testing Introduces Django’s TestCase features, setup_test_data, Django model factories, and queryset mocking.
  7. 19:04 Grocery Subscription Case Study Attendees plan tests for order submission, validation, payment, and shipping workflows.
  8. 21:27 Django Assertions Reviews assertions for querysets, forms, and database query counts, including checks for N+1 problems.
  9. 23:44 Serializers and Test Boundaries Considers whether serializers belong in unit tests and when mocking them can keep tests focused.
  10. 25:15 Django Test Utilities Surveys test decorators and command-line options for configuring, selecting, debugging, and parallelizing tests.
  11. 29:05 Extending TestCase Shows how custom test case classes can reduce repetitive setup and standardize test settings.
  12. 30:36 Conclusion Summarizes the advice to use Django’s testing tools and shares contact information.

Transcript

4,348 words · auto-generated Show

Automatically transcribed, so expect mistakes in names and technical terms.

0:16

All right, let's talk about unit tests. Um, no, let's talk about myself Woo! Okay. Um so my name is Colleen Dunlap. I am a full stack developer. I attached my resume in the bottom right corner. Very full stack, very multi-talented. Just kidding. I'm mostly a back-end engineer. I work at a company called Hungry Root, rare grocery and meal kit service. Mostly grocery, but we have aspects of meal kitten for those of us who don't like to uh figure that out day of. If you're interested, you can scan that QR code and you should get $50

1:01

off your first order of $100 or more. I really like it. I use it. Um, I also work on it. So maybe I'm biased, but I don't know. I like food and they give me food. I don't think it's too uh too biased, kind of a human necessity. I've been using Django since 2022 across quite a different, a vast amount of jobs. I've been using Python since question mark. My first Python job was teaching girls who code at my local, my local, my university , to little kiddos, and it was really cool. Um, this year I recently became a Django contributor or what they call an

1:48

author, which feels weird to say because uh Compared to the size of Django, I feel like I've written very, very little of it. Um, but that's the cool thing about open source and community. Um, I'm also a circus artist, so if you're into aerial acrobatics, fire performance, contortion, all those stuff. Feel free to talk shop in the hallway after. Or if you think that sounds cool, I guess I could do a lightning talk later this week on it. Um, yeah, okay, cool. I'll do it. It doesn't take too much convincing to get me to talk about circus. It's a lot harder to get me to talk about software engineering, but here we are. Okay

2:33

I'm gonna get started now. All right, so what is a unit test? First off, has anyone written a unit test before? Wow, has anyone ran a unit test before? Okay, how many of you are confident that that test was the smallest unit of code that could possibly test be tested and be useful? What all right, totally fair. Um side note, I really love that um the keynote this morning was talking about season technologies and really hyping that up because I'm like, yeah. Unit testing, really sexy, right? Um, but it's super important.

3:18

It's one of the topics that you know software engineers use like every day. Every time you want to push a feature, you should be running unit tests. So I'm excited to talk about it. Um but yes, units uh of source code, they want to be tested on their own as individual components. This is opposed to a system or integration test that would test the entire system. And I think about unit tests as finding termites or bugs. Pun intended, um, in a plank of wood before using that piece of wood to build a whole frickin' house. Um, why unit test the code as opposed to integration testing or what have you? As I mentioned, it focuses on small units of code, which means it's very specific.

4:08

Um they are identifiable, they are isolated, um, they provide fast feedback and TDD. So the nice thing about a unit test is determinism. It means it can be reproducible, repeatable. You should get the same result. every time you run a unit test, if you don't break the code that is running it. What I like about a unit test too is if something does go wrong, your unit testing framework generally documents. uh where the error is as opposed to an integration test where if you run your five plus five function and all of you suddenly get 26. Depending on how long that function, that program

4:55

is, it could be anywhere. So you get really fast specific feedback with unit tests. If you went to college for software engineering or are just familiar with the blogs, you can often hear about test-driven development or TDD. It does not stand for tiers -driven development despite uh common knowledge. It technically, at least first, was test-driven development, which means you write your tests first as kind of a planning methodology, and then you write the source code to make The test pass and the test kind of sets up the requirements, the acceptance criteria, so you get feedback on whether you're designing your source code right.

5:48

So, when we unit test, what do we actually want to test? There's two kinds of schools of thought. One is implementation, and the other is behavior. So testing for behavior means we're testing for what does the code actually do? If we put X into our as our input, do we get Y as an output This is kind of like a black box. You're not worried about how the implementation, how the source code is getting the result. We just care that the result and the behavior is what we expect. Implementation, on the other hand, how does the code do it? So as opposed to a black box, we have a completely see-through box. And this would be a question like, okay, we put X in

6:36

theoretically to get Y, but did we call become Y in order to get the Y? With implementation testing, you want to think how often do you want to rewrite the tests? Because naturally software engineering is complicated. That's why we all have jobs. Um that's why we get paid decent money to do things because it's complicated and it's hard. And oftentimes they say all writing is rewriting. I feel like that happens with code a lot as well. So as your source code changes, how badly do you want to rewrite the tests? Your strategy for coding often changes more click

7:22

quickly than requirements ideally. But there are uses for implementation testing as well. One example of that is the N plus one problem. So here we have a little bit of Django code. The first one, um takes all of the orders then in a query set we traverse the query set and then um for each order we get the customer Under the hood, however, what's happening is we do a select from the orders and then for however many orders you have. Um, hopefully your business isn't doing well, so that's a crap ton. Um, you're going to do a separate select sys uh

8:07

query. getting the customer for that order. That's the N and the N plus one. The one is the order and then the N calls are the crap ton of customer select statements. However , there's another way to write this that is with select related. There's some couple other strategies, but basically you're doing a join and then you have one transaction from the database And as you traverse orders, no additional select queries because you got them all in that original database transaction. So Unit tests are really great because this isn't super obvious, at least the way you learn to code like in your object-oriented classes.

8:54

It might not be super obvious that this is calling a crap ton of queries on the top, but your unit test knows And we're going to talk about the assertions that it can do to tell you. So there definitely is a purpose for testing for implementation as well. All right, mocking. Everyone asks who's the imposter, but no one asks how's the imposter. So mocking is a technique to isolate the code being tested from its external dependencies. It is good for the isolation part of the unit test. It is good for the determinism part of the unit test, making it reliable and reproducible. It's also helpful for triggering different scenarios.

9:41

If you have kind of a decision tree code pathing function. You can use a mock to make sure that your unit test takes a certain path. And it's good for placeholders, as we said. You can test driven development. Maybe you need to uh write a function before one of its dependencies has been produced, you can mock the placeholder. Mock in placeholder that third-party dependency. It's also great for implementation testing. We have some assertions, assert called with, assert called once, which will ensure a certain object or function was called as your source code was executing. Here's an example. So

10:26

say we have a function that takes in a random number. With unit tests, we like things to be uh reliable and repeatable. So random is not reliable and repeatable. They both start with R's, I get it, but they are different things. So what we can do is we can patch that random int function that get random int and square will call. Set the return value to five. So instead of actually getting a random number, random int always returns five. And then we can assert that it always equals 25 And then you can also assert that that random in function was called. So we did get a random number,

11:12

but we can assert that the implementation was correct. Setups class, teardown class. Um a lot of times unit tests cover a wide variety of partitions, so you might be using certain objects Very often it can get pretty repetitive. That's why unit test has set up class and teardown class. I think this should have been in my introduction slide, but Uh Django test is oh never mind, we're getting to that. Ha ha. Okay. Adderall work works really well until it doesn't, guys. Okay, so Setup class, teardown class. Let's go back to what the slide is about. So setup class is a function that is won

11:58

once before all tests in a test case class. This is really good for things like seeding things that aren't going to change between each class. Or each test function, excuse me. Um setup, however, is ran before every test in a test case An example of that could be creating a fresh order for each test because you want a fresh order. And then teardown class like setup class is ran once after all the tests in the test case have ran, and teardown is ran after each test in the test case. So you can also see those at the bottom. Setup class and teardown class are class methods where setup and teardown

12:45

get executed after each test. Factories, factories are how we get test data. It's not the only way you can do test data, but it's a really great generator. What it does is it modularizes the creation of test data. It abstracts out unnecessary table details and they also allow you to do subfactories to create um relations. So If all of your orders have a customer, you can create an order, but then also in that factory specify creating the customer as well. We'll use Factory Boy during this talk. But an example of this is if you're testing a function's ability to process or charge an order, you probably don't care what that order is specifically populated with

13:36

, just its total and status. So you don't actually have to set up that those box, those uh order contents. Here's some sample code. You can use the faker library within factory to create things like fake names, emails, phone numbers And there at the bottom we have an example of a subfactory. The model, that's not necessarily a Django model, that's the data model you're specifying. Do do do cool. Alright, now do it with Django. This was the thing that I was gonna talk about when I started talking about something, and I was like, let's talk about the slide. So Django. test dot test case, that is the standard Django test case that we all probably use, and it is a subclass of a unit test

14:27

taught test case. So if you see me using unitest in the example code, et cetera, that's one because That's what I use, so I'm selfish. And I agree that you should talk about what you know. Um, but also because Django itself uses it. So here's an example of how you would take in the Django test case to create your own test cases and as you see we are using inheritance so when we write our own setup class method we are inheriting from the Django dot test test case dot setup class

15:13

Alright, so let's talk about all the cool things we can do now that we are using Django and not fighting Django in our unit tests. So first up we have setup test data. So like setup class, this is ran once per test case. However, um in our setup test data, we create model instances. And all the model instances that are created in setup test data are rolled back after each test execution. This is faster than recreating the same model before every test case as setup does. I see some with a phone and they want to take a picture. Okay, I'm gonna flip it now. Alright, so without setup test data, um we have the setup function, this

15:59

gets ran um every time we have a new test case. So if test inventory service, for example, had 50 test functions, it would get ran 50 times. We'd create and delete 50 objects. However, with setup test data, not only do we not have to write as many functions. But uh we will get that fresh order each time we run the test without having to do do do words. Without out having to delete it, recreate it, delete it, recreate it, which saves us a lot of time Django Model Factory. So this is a um class in Factory Boy.

16:45

Fun fact, Factory Boy originally was developed for Django and evolved to be framework independent, which I think is really cool. But Django Model Factory allows for more field types. So image field, file field are two examples. Those are fields that can be on a Django model. So it's really nice that Django model factories can do those. You also have the option to mute signals. So for example, if we have a pre-save function on order model. to make a customer object when a user places an order for the first time, we can mute the auto creation of a new customer. So that the test customer uses a sub factory

17:31

instead of the pre-save function. And you use that with a decorator, factory. jjango. muteSignals And here is our factory now that we are using Django Model Factory as the base class. So you can still use object notation. So App. order, you could import the order model from app. models, um, but you can also use the string notation as well. Um And then we get to use things like file field to um create a file field. But what's cool about that is if in your

18:18

testing you need to use that field in your testing, it behaves as a file field would. So you can read the file, export, etc. All of those functions. Same with image. Mocking. This is a cool little thing I learned in my research. Um, but there is Django mock queries. So, this is a separate library and it creates mocked query set objects, which support query set operations, order by filter, etc. But it's cool because they simulate ORM transactions without a database connection. So if you have a really expensive query and that's not really the point of your unit test.

19:04

You can use Django mock queries to mock that out. Also want to note, you absolutely can use mock in its standard sense. with a query set. You can define a query set and then set that equal to the return value or the side effect of a mock. And use that just as well. What's special about Django mock queries is that it doesn't require a database connection. All right. So um I'm gonna put you all to work. So we have a case study uh grocery subscription service. Maybe it sounds familiar. Um, I talk about what I know. Um, but

19:51

we are going to design our own test plan, maybe write out some test cases. You can work with the people next to you. It is a conference. You can confer Um, yeah, I don't know. Pun intended? Is that a pun? I think that's just Latin. Okay, so Let's talk about the case study. We have some models. We have an order model, a customer, inventory, and product models. I have defined kind of some important parameters on all of those models in these little boxes. I want you to note the dot dot dot means there's a crap ton of other fields. Um but you can, you know, use your factory

20:39

knowledge To use those, ignore those fields as you'd like. And we're gonna write tests for four different functions. We have submit order, which is an API post. Validate order, which is a back-end function to ensure order is sufficient. Charge order is a back-end function as well, which communicates with a payment API. And ship order generates a shipping label via a shipping API for our fulfillment center to use. So I'm gonna give you guys five minutes and you can talk amongst yourselves Think about what factories you would use. Think about what you would mock. Think about what sort of functions you would write, what partitions you would write

21:27

Um yeah, okay. Break. Hello, it's been five minutes. I won't subject you to socialization any longer. Um, but I did hear people talking, which is really exciting. Um, I heard someone say mock and I hope they weren't mocking me. Um, I think they were talking about code, so I'm gonna call that a win. All right, assertions. So we have some special assertions that you can use in Django. Um this is by no means a complete list. Um but they are helpful. So assert contains. This takes in a query set and makes sure that it contains a specific

22:13

object that you're looking for. Assert form fails. This is Django form, so you're asserting that the form fails given a certain input um or action , whatever you're testing, whatever your source code is really. They also have you know, a certain forum not fails. I don't know if that's actually the name. I should have written it down. But I assume if uh they're testing for failure, we also test for success. Cause optimism. And assert numQueries. This is what I talked about a little bit back with the mocking. Um, so, or well, assert um numQueries Uh

22:58

this is really helpful for testing N plus ones. Um you run your source code and then assert. check how many queries were ran. Now for more complex source codes, you might not want to assert an exact number of queries. But you definitely will want to kind of set a ballpark. So for example, do you want your test to break just because you've added another table that takes one quick select statement? Maybe not. Um, but do you want your test to break when you call crap ton orders Um yes, probably. So you can assert queries is less than

23:44

as well. You can also assert queries are more than one, but you can also use mocking functions for that. So assert num queries is definitely helpful um for testing for implementation before things get out into the wide world known as prod environment. Serializers. So serializers are part of Django REST framework. Um they're a way of taking uh requests. And translating those into models. But are you sure you want to call them in your unit test? Are you sure you want to test them in your unit test? Should your unit test fail because the model makeup

24:29

changed on the model that you see realizing? So uh case study example Submit order utilizes Django Rest. One day we decide that the order model should have a rating field for the customer to rate after they received their order. Now, when we submit the order for uh purchase, they probably don't have a rating yet. Why would you rate something that you haven't even paid for? Um, so do you want your serializer to fail? Because You it doesn't have a rating field present yet, um, or would you want to mock that serializer in order to kind of contain it Um decorators, decorators are really helpful.

25:15

Martha approved. Had to throw in a picture of the internet's favorite interior decorator there. But these are code decorators. So you can put these on top of your test functions to make things a little bit easy peasier. One is override settings. This overrides the Django. conf settings. Modify settings, this allows you to prepend or append to certain settings, e. g. middleware Django dot test dot tag tags um that allows you to tag a test. So you only ran it when that tag is present or can filter out uh different tags during the test run.

26:00

Isolate apps app label. This actually goes on top of any model created uh just for your testing purposes. Um so it creates a new app label um specifically for tests. So you is you create a model that's only to be used in tests. You can decorate it with isolate underscore apps, test app, for example. And then Skip is a decorator for unit test. It skips. uh you know it skips so if you want to skip a test you can um skip it under certain scenarios um there are two that are Django specific though

26:46

um skip if slash unless db feature. So since Django is uniquely tied to the database, you can skip tests under certain database configuration scenarios. Utilizing manage. py to invoke tests, um had to include a Dilbert comic here. Um, I love my managers. I really do. Uh, all in good fun, but um, you know, it's still Bert. It's a Django conference. I had to. Um, but You can add these fun little arguments options to your invocation of the test

27:32

to make things a little bit easier. So python manage. py test plus uh we have parallel. This allows you to run your tests in parallel. You just Add an integer after parallel and it'll create multiple db instances and run your tests in parallel, making it faster. Keep db, you would add this if you wanted to keep the test database after your tests have ran. It's nice for um debugging. No input. This is what you would put on the end of a invocation if you did not want to be asked if you wanted to

28:18

recreate the database. So if your unit test failed or you control seed out of your unit test run early, you can use no input so it knows to just recreate the test database instead of prompting you, do you want to create the database again? Cause I don't know about y'all, but I've I've wasted minutes in my career, um, thinking my tests were running and it was just waiting for me to uh tell it to recreate the test database. So helpful. Um tag. This is how you would only run tests that have a certain tag defined. PDB , you might not need this depending on what test framework you're using.

29:05

But definitely for PyTest you would use PyPdB. in order to utilize breakpoints, set trace, all that good stuff in your test run, and dash K to run a single test at once. And extending test case. So the joys of object-oriented programming. Um if you're like us I'd like to think we have a very healthy unit test suite, but that means that we have a lot of very healthy unit tests. Unit tests use objects and um you know every industry has the specific like

29:50

models data types that are gonna get used a lot You can use, you can extend test case to make your frequent use case efficiency a little more efficient So here we have an example. We wrote base test case with customer. Basically all this does is it overwrites test case. And it sets up a customer. So instead of having to write our own setup test data functions using the Django test case, we get to just use base test with customer and that also makes reading things a little easier um makes things a little cleaner makes your job a little less repetitive um

30:36

you can also include boilerplate test setup settings, etc. in your extended test case. For example, if you want to not use cache. So if you want to silence the cache, you could do that in a extended test case. In conclusion, uh if you're not using unit testing, you should start. And if you've started I suggest you work with Django and not against Django. If you want to keep in touch with me, that's my Medium blog. And in case you really enjoyed this talk and are hungry, that is once again my referral code for Hungry Roots. There is a QR code talk. And that's like either tomorrow or the next day.

31:23

And that made me kind of nervous about putting QR codes in my slides. So I also included the links as well that you can just put into your little browser. Um sweet. If you have any questions, I'll be sticking around. You can just come up to me and ask. I promise I'm friendly. Um, and yeah, happy Django Con

Questions this talk answers

What is a unit test, and why should I use one?

A unit test checks a small, isolated piece of code rather than the whole system. It gives fast, specific, repeatable feedback when something breaks, and can help support test-driven development.

Discussed at 3:18

What’s the difference between behavior testing and implementation testing?

Behavior tests check whether given inputs produce the expected outputs, without caring how the code works internally. Implementation tests check how the code achieves the result, such as whether it calls a particular function or avoids excessive database queries.

Discussed at 5:48

When should I use mocks in unit tests?

Mocks isolate the code under test from external dependencies, make tests more deterministic, and let you force particular code paths. They can also stand in for dependencies that are not built yet or verify that a function was called.

Discussed at 8:54

What’s the difference between setUp, setUpClass, tearDown, and tearDownClass?

setUp and tearDown run before and after each test, while setUpClass and tearDownClass run once before and after all tests in a test case class. Use the class-level methods for shared setup that doesn’t change between tests, and per-test methods for fresh data each time.

Discussed at 11:12

How do factories make it easier to create test data?

Factories centralize and simplify test-data creation, hide model details a test doesn’t need, and can create related objects through subfactories. Factory Boy can also generate placeholder values with Faker.

Discussed at 12:45

How does Django’s setUpTestData differ from setUp?

setUpTestData creates model instances once for a test case, and Django rolls back changes to those instances between tests. This can be faster than recreating the same data in setUp before every test.

Discussed at 15:13

How can I test for an N+1 query problem in Django?

Use Django’s assertNumQueries to check how many database queries the code runs, or set an upper bound when the exact count may change. This can catch code that issues a separate query for each related object.

Discussed at 22:13

What options can I use with manage.py test to make test runs easier?

The talk covers options including parallel runs, keeping the test database for debugging, skipping interactive prompts, filtering by tag, and running one test with -k. It also mentions using pdb for debugging, with PyPyDB noted for pytest users.

Discussed at 26:46

Presenters

Note: 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.

More videos from DjangoCon US