Django Testing Tutorial with Pytest #2 - Unit Testing (2018)

The Dumbfounds
28 Mar 201814:12

Summary

TLDRThis tutorial offers a step-by-step guide on setting up and writing tests for a Django project. It begins with configuring a 'settings.py' file for testing, then moves on to creating test files for URLs, models, and views. The script demonstrates testing URL paths, model functions, and view access with both authenticated and unauthenticated users. It also touches on using the 'mixer' library for creating mock data and emphasizes the importance of testing to ensure project robustness.

Takeaways

  • 📝 Start by setting up a file named 'pi_tests.py' in the home directory to specify the Django settings module for tests.
  • 🔧 Consider creating separate settings for production and testing to avoid issues like sending emails during tests.
  • 📁 Create a 'test' folder to house all test files and a 'tests_urls.py' file to begin testing URLs.
  • 🔗 Use Django's 'reverse' function to get the URL path for a given view and 'resolve' to check if the URL resolves correctly to a view.
  • 📝 Write test functions within a class dedicated to URL tests, ensuring each function has a single assert statement for clarity.
  • 🛠️ Utilize 'mixer' to create instances for testing model methods without manually specifying all model fields.
  • 🔍 Test model methods like 'is_in_stock' by creating product instances with varying quantities and asserting the correct outcomes.
  • 📑 In 'tests_views.py', write tests for views ensuring they behave correctly with authenticated and unauthenticated users.
  • 👤 Test views that require user authentication by mocking requests with an authenticated user and checking for the correct response.
  • 🚫 For unauthenticated user tests, assert that the response redirects to the login page and contains the expected URL.
  • 🔄 Address database access issues in tests by using the '@pytest.mark.django_db' decorator to enable database access for test cases.

Q & A

  • What is the purpose of the 'PI tests.py' file in the Django testing setup described in the script?

    -The 'PI tests.py' file is used to configure the test environment for a Django project. It specifies the settings module to be used for testing, which is typically different from the production settings to avoid side effects like sending out emails during tests.

  • Why is it recommended to create a separate settings file for testing in a production environment?

    -Creating a separate settings file for testing in production helps to avoid unintended consequences such as sending emails or making changes to the database that could interfere with the live system.

  • What is the significance of the 'reverse' function in Django testing?

    -The 'reverse' function in Django is used to obtain the URL path for a given view name and parameters. It's the equivalent of the URL in the templates and is used to ensure that the URLs are correctly resolving in tests.

  • What is the role of the 'resolve' function in the context of testing URLs in Django?

    -The 'resolve' function is used to revert a URL path back to a view function. It's essentially the opposite of the 'reverse' function and helps in testing that the correct view is being called for a given URL.

  • Why is it suggested to have only one assert statement per test function, and when should you split it up?

    -Having one assert statement per test function makes it easier to identify what each test is checking. If a test needs to perform more than one assertion, it's recommended to split it into multiple functions to keep the tests focused and easier to maintain.

  • What is the purpose of the 'mixer' library in the context of creating test instances for models in Django?

    -The 'mixer' library is used to create test instances of models quickly and easily. It allows for the specification of which fields should be filled in, simplifying the process of setting up test data for unit tests.

  • How does the 'is_in_stock' function work in the product model, as described in the script?

    -The 'is_in_stock' function in the product model checks whether the quantity of a product is greater than zero. If the quantity is greater than zero, it returns True, indicating that the product is in stock.

  • What does the 'Django DB mark' decorator do, and why is it used in the test functions?

    -The 'Django DB mark' decorator is used to mark a test as requiring access to the database. It ensures that the test will run with a real database connection, which is necessary for tests that interact with the database.

  • Why is it important to test both authenticated and unauthenticated access to views in Django?

    -Testing both authenticated and unauthenticated access ensures that the view behaves correctly under different user conditions. It helps to verify that authentication requirements are enforced and that unauthenticated users are redirected appropriately.

  • What is the expected outcome when an unauthenticated user tries to access a view that requires authentication in Django?

    -When an unauthenticated user tries to access a view that requires authentication, the expected outcome is a redirect to the login page. The test should assert that the response includes a redirect to the login URL.

  • How can you measure test coverage in a Django project, as hinted at the end of the script?

    -Test coverage in a Django project can be measured using tools like 'coverage.py', which analyzes the parts of the codebase that are covered by tests. The script suggests that future parts of the tutorial will delve into this topic.

Outlines

00:00

📝 Setting Up Django Test Environment

This paragraph introduces the process of setting up a testing environment for a Django project. It begins by creating a 'PI tests.py' file in the home directory to specify the location of the settings file for the tests. The tutorial suggests using 'testing_top_settings' for the 'Django_settings_module'. It also mentions creating a separate settings file for production to avoid issues like sending out emails during testing. The focus then shifts to creating a 'test' folder and a 'tests_urls.py' file to start testing the URLs, specifically the 'intBK' path which is assumed to be working correctly. The paragraph explains the use of Django's 'reverse' and 'resolve' functions and outlines the structure of a test class and a test function to verify the URL path.

05:02

🔍 Testing Django URLs and Model Functions

The second paragraph delves into writing tests for URLs and model functions within a Django project. It describes importing necessary functions and setting up a test class for URL tests. The function 'test_detail_url' is explained, which uses the 'reverse' function to generate a URL path and the 'resolve' function to ensure the correct view is being called. The paragraph also covers the best practice of having a single assert statement per test function. Moving on to model testing, it introduces the 'test_models.py' file and the creation of a test function 'test_product_is_in_stock' using the 'mixer' library to create a product instance with a specific quantity. The function tests the 'is_in_stock' method of the product model, asserting its correctness based on the product's quantity.

10:05

🛡️ Authenticating User Access in Django Views Testing

The final paragraph focuses on testing user authentication in Django views. It outlines creating a test function 'test_product_detail_authenticated' to verify that authenticated users can access a specific view, which is protected by a 'login_required' decorator. The test involves using the 'RequestFactory' to create a request object with an authenticated user and then calling the 'product_detail' view function with this request. The response's status code is checked to ensure it's 200, indicating successful access. The paragraph also discusses handling database access issues during testing by using the '@django_db' decorator to mark tests that require database access. Additionally, it covers testing for unauthenticated users, expecting a redirect to the login page, and asserts that the login URL is part of the response's redirect chain.

Mindmap

Keywords

💡Django

Django is a high-level Python web framework that encourages rapid development and clean, pragmatic design. In the context of the video, it is the framework within which the tutorial is being conducted, focusing on setting up and writing tests for a Django project. The script mentions setting the 'Django_settings_module' which is a configuration setting for a Django project.

💡Testing

Testing in software development is the process of verifying that a system or component performs as intended. The video script discusses the importance of testing in Django projects, starting with setting up a 'tests.py' file and writing tests for URLs, models, and views to ensure they function correctly.

💡PI tests

While 'PI tests' is not a standard term in software development, within the script, it seems to refer to running tests in a Python environment, likely using the 'pytest' framework, which is a tool for writing simple and scalable test cases in Python. The script instructs to run 'python tests' from the command line to execute the tests written.

💡URLs

In web development, URLs (Uniform Resource Locators) are used to locate resources on the web. The script talks about testing URLs in a Django project, specifically checking if the 'detail' URL path is correctly resolving and reversing, which are functions used to generate and check the correctness of URL paths.

💡Models

In Django, models are the single, definitive source of information about the data and the rules surrounding it. The script describes creating tests for a 'Product' model, specifically testing the 'is_in_stock' method, which is a function of the model that determines if a product has stock available based on its quantity.

💡Views

In Django, views are functions or classes that take a web request and return a web response. The script discusses testing views to ensure they behave as expected, such as checking if a 'product detail' view requires authentication and responds correctly to both authenticated and unauthenticated users.

💡Mixer

Mixer is a library for creating random test data for use in testing. In the script, Mixer is used to create instances of models with specific attributes, such as a 'Product' instance with a fixed quantity, to test the 'is_in_stock' method without manually setting all the fields.

💡Request Factory

A request factory in Django is a tool for creating dummy requests in tests. The script uses 'RequestFactory' to create a request object that is then used to test views, simulating an authenticated user accessing a view and checking the response.

💡Test Decorators

Test decorators in Django are used to apply certain settings or behaviors to a test function or class. The script mentions using the '@pytest.mark.django_db' decorator to indicate that a test requires access to the database, which is necessary for testing views that interact with the database.

💡Redirection

In web development, redirection is the process of sending a user from one URL to another. The script discusses testing for redirection in views, specifically checking that unauthenticated users are redirected to a login page when trying to access a protected view.

💡Anonymous User

In Django, an 'AnonymousUser' is a default user instance representing an unauthenticated user. The script uses 'AnonymousUser' in tests to simulate access to a view by a user who is not logged in and to check that the view redirects them appropriately.

Highlights

Introduction to setting up a file called 'PI tests.py' for the initial testing setup in the home directory.

Explanation of specifying the Django settings module in the 'PI tests.py' file for testing purposes.

Creating a 'test' folder and a 'tests_urls.py' file to begin testing the application's URLs.

Importing 'reverse' and 'resolve' functions from Django's 'urls' for URL testing.

Writing a test class 'testURLs' for organizing URL tests within.

Testing the 'detail' URL using the 'reverse' function and checking its correctness with 'resolve'.

Running the first test with 'Python manage.py test' and observing the test execution.

Creating a 'tests_models.py' file for model testing and focusing on the 'is_in_stock' function.

Utilizing the 'mixer' library to create product instances for testing the 'is_in_stock' method.

Writing unit tests to check both true and false conditions for the 'is_in_stock' function.

Using the 'Django DB' mark to handle database access in tests.

Creating a 'test_views.py' file to test views, starting with the 'product detail' view for authenticated users.

Mocking a request with an authenticated user to test the 'product detail' view's access control.

Asserting the correct status code (200) for the 'product detail' view when accessed with proper authentication.

Testing the 'product detail' view with unauthenticated users and expecting a redirect to the login page.

Asserting that the redirect URL contains 'accounts/login' for unauthenticated access attempts.

Demonstration of running the test server and manually verifying the redirect behavior in a browser.

Invitation for feedback on the tutorial series and a teaser for the next part on test coverage measurement.

Transcripts

play00:00

hey ole I hope your day is going great

play00:02

and in this tutorial we will start by

play00:04

writing our first tests so the very

play00:08

first thing we need to do is set up a

play00:10

file called PI tests though in E and

play00:14

this file just sits in the home

play00:16

directory and this is where we are going

play00:18

to tell PI tests where our settings file

play00:21

will be located so start with PI tests

play00:25

in these square brackets and then you

play00:28

can specify the Django underscore

play00:30

settings module and I'm just going to

play00:34

set it to testing top settings which is

play00:37

- standard settings fyra here and if I

play00:40

were in production now I would probably

play00:41

create another one just for testing

play00:43

because you know if you're sending out

play00:45

emails or something like that it could

play00:47

mess up but um that will be fine for now

play00:49

and then we can go into the product and

play00:53

first of all create a new folder and

play00:56

call it test which is where all of our

play01:00

test files are going to sit and then

play01:02

create a new file and the first thing we

play01:05

want to test out the URLs so I'm going

play01:07

to name this file tests underscore URLs

play01:09

but PI let's just go back to the US of

play01:13

Pi file so see what's going on so

play01:15

basically you want to test this path

play01:17

right here because this one is just

play01:19

default from Jenga and we'll just assume

play01:21

that it works fine so we want to test

play01:25

that this int BK path will always work

play01:27

and as you can see we have assigned the

play01:30

name of detail to it so that will also

play01:32

help now while testing and first things

play01:36

first we want to import from Django tour

play01:40

URLs the functions called reverse and

play01:43

resolve and we're going to discuss what

play01:46

they do in a second and I'm going to

play01:50

start out with the class called test

play01:53

URLs and this is where all of our tests

play01:55

for the URLs are going to set in this

play01:57

class right here and then we want to

play02:00

write a function and call the test

play02:02

underscore detail URL so in this one we

play02:07

are simply just going to test detail URL

play02:09

and a very good practice is to have one

play02:11

assert statement perform

play02:13

so if more than that you should probably

play02:15

split it up into two functions and it

play02:18

takes on yourself and now we can first

play02:23

of all get the part of the URL so set

play02:26

the path equal to reverse detail and the

play02:30

reverse function is simply just the

play02:32

equivalent of the URL in the templates

play02:35

so we pass at the name and then it gives

play02:37

us the path back and as you can see in

play02:40

this case a path is a parameter so

play02:43

that's why we also have to pass it one

play02:45

value for it and I'm gonna set the

play02:48

keyword arguments equal to PK which is

play02:52

how we named it and then it expects an

play02:55

integer and this one will be one because

play02:58

that's the standard integer for one

play03:00

object and it's where the auto increment

play03:03

field starts and then simply we can

play03:08

assert that the resolved path don't view

play03:13

name is equal to detail and resolved

play03:17

path basically just or the resolved

play03:19

function in general takes in a path and

play03:21

then from there reverts back to the

play03:23

function so it's kind of the opposite of

play03:25

the reverse function and then we simply

play03:27

call the view name and assert that we

play03:29

are calling the correct function so that

play03:34

will be it basically a for this function

play03:36

and go back to your command line now and

play03:38

now you can run Python tests and it kind

play03:48

of takes a moment yeah and you will see

play03:50

that our test ran through a my goal with

play03:53

this part and the next part will be

play03:56

really to get you guys up and running as

play03:58

quickly as possible and after that we

play04:00

are going to dig deeper into files like

play04:02

this one and understand what's going on

play04:04

right here but I really want to get you

play04:07

up and running as quickly as possible so

play04:08

you can start running your unit tests so

play04:11

next up we have the models which we need

play04:15

to test as well so create a new file

play04:16

called tests underscore models stop key

play04:19

Y and we're going to do the inputs in a

play04:23

second first of all start with the class

play04:25

of test

play04:27

and that's just a convention I like to

play04:29

use basically just with the perfect of

play04:31

tests and then whatever I'm going to

play04:33

test so in this case we want to create a

play04:38

function and call it tests underscore

play04:40

product is in stock text itself you can

play04:46

remember in the model so PI we had a

play04:48

function called isn't stock so that's

play04:50

what we are going to test with this unit

play04:52

tests and now we have to create a

play04:57

scenario where we are going to assert

play04:59

that the function works correctly so we

play05:01

want to basically create a product

play05:03

instance and as you can see a result of

play05:06

the isn't stock function just depends on

play05:08

the quantity so that's what we are going

play05:10

to mimic and we could do it in a way

play05:14

where we say like product equals product

play05:17

the object stop great and then pass all

play05:20

of the you know fields manually but that

play05:23

will create a lot of overheat because we

play05:26

really only want the quantity field and

play05:29

that's the only thing that matters what

play05:30

this test really and that's why we

play05:32

imported or why we installed mix in the

play05:35

last part because mixer allows us to

play05:38

basically just say mixer blend and then

play05:41

we can specify the the name of the app

play05:44

which is product and then dot D model

play05:48

which is product and then we can pass

play05:50

all of the fields which we want to have

play05:52

fixed in this case it's only quantity

play05:54

and sell equal to one and now we want to

play06:00

assert that whenever I call D is in stop

play06:03

function on this product it returns true

play06:05

because of course it's greater than zero

play06:08

and that was our definition of returning

play06:10

true

play06:10

so assert that product though is in

play06:15

stock is in stock is equal to true and

play06:20

now we are going to make the imports so

play06:23

from products products top models import

play06:28

product oh actually we don't need

play06:31

product yeah because we're using yeah we

play06:33

don't need that from mixer top back end

play06:37

django import mixer

play06:40

now go back into your comment on and you

play06:43

will see an error if we run it because

play06:46

we are we don't have database access so

play06:49

we can as you can see it already gives

play06:51

us defects which is to use Django DB

play06:53

mark and we are going to get into easy

play06:56

ways of fixing that later on but for now

play06:59

just import PI test and then we can add

play07:03

it directly to the class instead of on

play07:05

top of every single function so simply

play07:09

use a decorator height as marked or

play07:12

Django DB any will see that we ran it

play07:19

successfully and if we we're to assert

play07:23

that this should be false we would we

play07:25

should get an error and with you so

play07:28

that's working fine and then I'm simply

play07:31

just going to copy this whole function

play07:35

and then paste it right underneath and

play07:37

call the test product is not in stock

play07:41

and in this case we want to set the

play07:43

quantity equal to 0 and then assert that

play07:46

it's false because of course we are

play07:49

returning saft or quality is greater

play07:51

than zero and in this case it is zero so

play07:54

that should be false and yet that works

play08:00

as well now we can also create a new

play08:02

file and name a test and go use the fire

play08:06

and this is where our view tests are

play08:09

going to set again start off with the

play08:12

class of tests views and now we can

play08:17

create a function called test product

play08:19

detail authenticated clicking the self

play08:24

and in this function we want to test

play08:26

whether we can exercise when we are

play08:28

authenticated because as you can

play08:30

remember inside of the views file we

play08:32

specified the log-in required decorator

play08:34

to be able to access this view and of

play08:37

course we want to make sure that it does

play08:39

what we wanted to do so first of all we

play08:41

want to test it with the authenticated

play08:43

State and what we have to do in this

play08:46

case is set a path equal to reverse

play08:49

detail and again pass the keyword

play08:55

piqué goes to one you already had that

play08:58

and now we want to set the request equal

play09:03

to requests factory so we are just

play09:08

creating a new instance don't get which

play09:11

is a method on the request factory

play09:13

object half and then we want to modify

play09:18

the request to passing the authenticated

play09:20

user so simply set request the user

play09:24

equal to mixer blend user so that

play09:29

creates a new user instance and now we

play09:34

want to gather the response because

play09:36

we're just really mocking the access of

play09:38

a view so set the response equal to

play09:41

product detail and of course product

play09:46

detail is the function which we have

play09:49

inside of the views this one and as you

play09:52

can see it expects a request and a

play09:54

private key we already created the

play09:57

request so we can simply pass it a

play09:59

request and any private key of one and

play10:04

now we want to assert that a response

play10:07

top status code is equal to 200 so that

play10:12

it ran through correctly and we can next

play10:16

up make all of the inputs which we need

play10:17

to first of all from general test input

play10:21

the requests factory package then from

play10:26

general URLs we want to import reverse

play10:30

from Django contrib but all the models

play10:35

we want to import in user model except

play10:37

what we that is what we are using right

play10:39

here can remove these quotes actually we

play10:42

don't need them and then from products

play10:47

top views we want to import the product

play10:50

detail and of course we are using mix as

play10:54

well so from from mixer or back end or

play10:59

Django want to import mixer

play11:03

and now go back into your comment line

play11:06

and run PI test and again the database

play11:11

access failed so just import PI test and

play11:15

then we can add the PI test or marked or

play11:18

Django DB no product matches to given

play11:25

query of course it doesn't because we

play11:27

try to give it the private key of one

play11:30

one but we don't have a product which

play11:32

has the property of one so we just are

play11:35

going to make an instance of a product

play11:37

and by default it's going to start with

play11:39

the private key of one just mix it all

play11:42

blend products top product let's check

play11:46

work now

play11:48

and yeah it runs through and now we want

play11:53

to basically do the same thing with

play11:56

unauthenticated users so def test

play12:01

product detail when we are fun Senta

play12:04

kated Oh

play12:08

what is going on yeah I'm just good to

play12:11

unauthenticated and the only thing we

play12:16

want to change really is this request so

play12:17

user inside it equal to anonymous user

play12:22

and we can import this one from the

play12:25

models as well

play12:27

anonymous user and actually it running

play12:34

that's right like this and you will see

play12:36

that we should get a three or two error

play12:38

and yeah we do because that's the

play12:41

standard code for a temporary redirect

play12:43

because after that we obviously want to

play12:46

get back or we want to get redirected

play12:49

back to the normal view as soon as we

play12:51

logged in so the 302 redirect really

play12:54

just redirects us to the login view and

play12:58

from that it redirects us back to the

play12:59

view we actually want to access but

play13:03

instead of doing that we are just going

play13:04

to assert that accounts login is inside

play13:09

of the response dot URL

play13:15

and yet works and just for demonstration

play13:18

purposes I'm just going to run it server

play13:21

and I'm now going to exit it on my

play13:25

browser and if we we're now to go to

play13:34

slash one you will see that we get

play13:37

redirected to accounts logon and all we

play13:39

are doing in the test is basically just

play13:41

asserting that this redirect which just

play13:43

happened will always happen with this

play13:45

accounts login in the URL and that was

play13:49

fine so I'm going to call it quits for

play13:52

this part make sure to tell me the

play13:54

comments down below what you want to see

play13:56

inside of the series and what I can help

play13:58

you with so stay tuned for the next part

play14:01

where we are going to dig a bit deeper

play14:03

in how you can measure your test

play14:04

coverage and assert that everything

play14:06

inside of your daring or project is

play14:07

going to be tested and yeah see you then

play14:10

Cheers

Rate This

5.0 / 5 (0 votes)

Related Tags
Django TestingUnit TestsWeb DevelopmentURL TestingModel ValidationView FunctionsAuthentication TestsWeb TutorialCode TestingDjango Decorators