DEV Community

PyTest with Django REST Framework: From Zero to Hero

Lucas Miguel on January 27, 2021

Who is this article for? For developers that want to learn everything they need to become efficient and effective testers, that don't ha...
Collapse
 
svandegar profile image
Seraphin Vandegar

Hi!

Just wanted to say a huge thanks for this article.

I'm new to Pytests and in the middle of rewriting a project to DRF, in TDD. I've bookmarked your article and probably opened it close to 100 time since I started.

Thanks again!

Collapse
 
sherlockcodes profile image
Lucas Miguel

Thank you very much! This is what I intended it for :)

Collapse
 
renaudcepre profile image
Renaud Cepre • Edited

I really feel more comfortable with pytest and django since I read it. Especially for the general organization of the project and the split between integration, units and e2e tests.
great work !
Edit: Just one small thing: e2e tests are not detected by default by pytest. It finds only the files starting with test_ or ending with _test, so I think you have overridden the python_files property without specifying it in the article.

Collapse
 
marvinkweyu profile image
Marvin

Hi @sherlockcodes ,still trying to understand what is happening in your viewset tests.

I can see methods within it with mocker but I cannot see where it is being set. kindly advice.

Consider a scenario, I have a model viewset, within it, I have overridden the patch to update another model when a certain condition is met.

Collapse
 
sherlockcodes profile image
Lucas Miguel • Edited

I don't think I understand exactly what you are asking. What is is that you can't see where it's being set?

What I am mocking, with the

mocker.patch.object(
            CurrencyViewSet, 'get_queryset', return_value=qs
        )
Enter fullscreen mode Exit fullscreen mode

is the viewset's get_queryset method, so to block access to the database.

In your described scenario. What is it that you want to mock? If it is a method from the viewset, just replace the "get_queryset" part with the name of whatever method you are trying to mock.

Collapse
 
marvinkweyu profile image
Marvin • Edited

I can see 'mocker' passed as a param, but no import nor config for pytest.

Thread Thread
 
sherlockcodes profile image
Lucas Miguel

Because it's passed as a fixture to the test function. You don't need no imports. It's PyTest's ABC. If you make a basic pytest tutorial or go through this article there's no way you don't encounter fixtures. I encourage you to do a basic pytest tutorial and then go through this article. You should not have any doubts like this left. Hope I was helpful

Thread Thread
 
marvinkweyu profile image
Marvin

100%

Collapse
 
lyqht profile image
Estee Tey

Hello Lucus, I just picked up DRF recently, and I found your article while looking for DRF testing tools! Thank you for writing such an in-depth article. I'm wondering if this is still the preferred approach over the inbuilt rest_framework.test and why?

Thanks!

Collapse
 
sherlockcodes profile image
Lucas Miguel

Hi Estee. You either question the aproach or the toolsets. rest_framework.test is a toolset. A very limited one also. You can barely do anything I talked about in the article with that. You don't have the ease of the commands, nor the customizability of the test runner, nor the several handy plugins.

Collapse
 
lyqht profile image
Estee Tey

Oh i see! Thank you, that helps me in understanding better!

Collapse
 
malakarishan profile image
Rishan Malaka

Hey. First of all I would like to Thanks for this amazing article. I just try to follow you concept and when I trying to apply this with HyperlinkedModelSerializer testing I'm getting some issues. It would be really helpful if you can share CurrencyFactory class code snippet. That was the missing pease of this article.

Collapse
 
sherlockcodes profile image
Lucas Miguel

Hi Rishan, thanks for asking. Remember you can always make a working factory with model_bakery. Besides that, I unfortunately lost all my files regarding this project. Nevertheless it's the easiest possible factory to make. You would need to make a random string faker generator for each field, and limit the characters to 1 in for the currency symbol (i.e: "$"), 3 for the currency code (i.e: "usd"), and a bigger amount of characters allowed for the currency name (i.e: "US Dollar")

Collapse
 
1oglop1 profile image
Jan Gazda

Hi, thanks for the article!
I have a question about the project structure:
If I understand it correctly your structure looks like this

.
├── dj_project
│   ├── apps
│   │   └── app_1
│   └── settings.py
├── manage.py
└── tests
    └── apps
        └── app_1
            └── test_app_1.py
Enter fullscreen mode Exit fullscreen mode

I wonder if you could comment on a different approach where you put tests for each app inside the app, like a so:

.
├── dj_project
│   ├── apps
│   │   └── app_1
│   │       └── tests
│   │           └── test_app_1.py
│   └── settings.py
├── manage.py
Enter fullscreen mode Exit fullscreen mode

Thank you

Collapse
 
sherlockcodes profile image
Lucas Miguel

Hi Jan. You lose the ability of using pytest fixtures on a tree structure. Basically, with pytest, you can make a conftest.py file on a per folder bases and you can add fixtures using your own criteria.

Let me explain myself. Let's suppose I want to use a fixture for all tests for an app (ie: I often make an autouse fixture for mocking api calls in every app and like to use another fixture to unmock it for the app it's managing the utils with API calls for integration tests). If I use the aproach you mention, I'll lose this ability. I of course might be missing more stuff, but it's the first thing that came to my mind. You should still manage to configure pytest to activate autoused fixtures on different projects, but there are many use cases that rely on this particular file structure you may will have to encounter workarounds on pytest conf. If you are really interested in this we can talk about different testing folder structures and pro/cons

Collapse
 
ori75660 profile image
Ori Roza

I liked your article!
Im inviting you to use my new package for rest framework called drf-api-action and write about it

which is designed to elevate your testing experience for (DRF) REST endpoints. this package empowers you to effortlessly test your REST endpoints as if they were conventional functions

github.com/Ori-Roza/drf-api-action

thanks!

Collapse
 
pyhimanshupatel profile image
Himanshu Patel

Thanks Lucas Miguel, for such a work
It took me 1 month to cover this. I started from 0 and now feeling like Hero 😂. Worth reading. Also if you can come up with one writeup on Mockup and Factories that would be highly appreciated.

Thanks Again Lucas. Great Work.

Collapse
 
sherlockcodes profile image
Lucas Miguel

Hi Himanshu. It means a lot to me! I'll add that to the backlog

Collapse
 
petrdanecek profile image
Petr Danecek

Hi Lucas,

I'm struggeling to make this part working:

class TestFilledTransactionSerializer:

    @pytest.mark.unit
    def test_serialize_model(self, ftd):
        t = FilledTransactionFactory.build()
        expected_serialized_data = ftd(t)
Enter fullscreen mode Exit fullscreen mode

where the ftd is defined or from where it is coming from?

Thanks!

Collapse
 
pingvincible profile image
pingvincible • Edited

Hi! Great article! I'm trying to write serializer tests for my code. When I call
assert serializer.is_valid(raise_exception=True)

my test_serialized_data asks for database connection.
But I don't want to do it because it is unit test.
Also I cannot mock is_valid method, because I'm testing it.
What should I do to avoid database connection?

Collapse
 
steelwolf180 profile image
Max Ong Zong Bao

That's great I was looking at a good article using Django and Pytest together.

Collapse
 
sherlockcodes profile image
Lucas Miguel

Glad it's useful!

Collapse
 
emiliocrespi profile image
EmilioCrespi • Edited

Hi thanks for you very useful post, I didn't get how can this snippet works, if FilledTransactionFactory.build() does not create database obj, t.id will be None. I missed something?

@pytest.mark.unit
def test_serialized_data(self):
    t = FilledTransactionFactory.build()
    valid_serialized_data = {
        'id': t.id.hashid,
Enter fullscreen mode Exit fullscreen mode
Collapse
 
sherlockcodes profile image
Lucas Miguel

It does build an object, it just doesn't hit the database to store it. It's like instantiating a common python object

Collapse
 
mohamedhamza profile image
Mohamed Hamza

Thanks for sharing this useful tutorial and I hope you can continue this series.

Collapse
 
vasantsachdewa profile image
VasantSachdewa

Hello Lucas,
Not sure if I missed out, but is there a way to do an e2e(integration) test without a running database?

Regards,
Vasant Singh Sachdewa

Collapse
 
sherlockcodes profile image
Lucas Miguel

Hey Vasant. Of course you could, it would imply mocking a lot of tuff (every unit involved) but if you read my article again you'll see there's no point in it. You basically would be missing the point, that is, keeping the e2e tests for sanity checks and having a test that merges the whole code.

Collapse
 
vasantsachdewa profile image
VasantSachdewa

Thanks Lucas, I really appreciate it

Collapse
 
ravi_yadav profile image
Ravi Yadav

Hi @sherlockcodes , Thank you for such a great article.

I just wanted to know if we can support multiple databases while running test cases in pytest-django.

By supporting multiple databases, I mean if there are two databases default and replica. Test cases store the data in default DB, but as the business logic code tries to access the data from replica DB. It got failed as it doesn't find the data there.

Thanks again!!!

Collapse
 
sahilxresta profile image
lowkey

hi it would be nice if you provided the github link to your code.

Collapse
 
sherlockcodes profile image
Lucas Miguel

Hi! There's no GH link. I made it for the post based on what I used in my company. If I make time for it I might attach one

Collapse
 
nikolaevra profile image
Ruslan Nikolaev

Lucas, have you had issues that coverage is not picking up your tested code because you are directly calling the API in your tests?

Collapse
 
sherlockcodes profile image
Lucas Miguel

I don't think I fully understand you. Could you elaborate that for me?