Jeff Triplett

How do I generate 1,000 objects in Django and DRF to test?

Filed under: django, django-restframework, drf, model-bakery, python

Earlier this week, Isa Buriticá asked me a great question about how to bulk create and test items in a Django test and I wanted to share that information on my website.

Question

I am using Django Rest Framework and I need to test an endpoint with at least 1,000 objects. But putting that in a cycle doesn’t seem very optimal to me.

Do you know of any way to create objects in bulk within the tests?

Isa Buriticá

Answer

My go-to library of choice for creating test data, more commonly called “fixtures” in Django tests, is Model Bakery.

With Model Bakery, you can pass in a Django model or model reference, and Bakery will create an object based on that model type. This fully-baked object includes valid test data so that each required field has the right type of data it needs.

Here is an example, based on their docs:

from model_bakery import baker

category = baker.make("news.Category")

assert category.name

To create 1,000 categories for a test, we pass the optional _quantity parameter to our baker.make method. Here is another example based on the docs:

from model_bakery import baker

categories = baker.make("news.Category", _quantity=1000)

assert len(categories) == 1000

Bonus Answers

Bonus Answer 1: It works well with pytest.fixtures

If you are using pytest and pytest-django, one pattern that I use all the time is to create a series of text fixtures for each of my models that I will be testing with.

tests/fixtures.py

import pytest

from model_bakery import baker


@pytest.fixture()
def category(db):
    return baker.make("news.Category", name="Category Name")


@pytest.fixture()
def post(db, category):
    return baker.make("news.Post", category=category, title="Post Title")

pytest fixtures are nice way to reduce the amount of boilerplate code you need for creating and testing objects. To use one of your new tests fixtures, you pass them in as a method argument and pytest will pass the fixture into the test function.

tests/test_models.py

def test_category(category):
    assert category.name == "Category Name"


def test_post(post):
    assert post.title == "Post Title"

For more information about pytest.fixtures, check out the pytest fixtures: explicit, modular, scalable docs.


Bonus Answer 2: It works well for generating test data

Model Bakery also has a prepare method which is useful for creating valid test data for a Django model without saving it to our database. I use this method often for testing Django Forms and DRF POST methods.

from model_bakery import baker

from news.serializers import CategorySerializer


def test_category_post(tp):
    # Create a Category fixture
    obj = baker.prepare("news.Category")

    # Use a DRF ModelSerializer to give us JSON
    payload = CategorySerializer(obj).data

    # Use a reverse lookup to find our endpoint's url
    url = tp.reverse("category-detail")

    # Login as a valid test user
    tp.client.login()

    # Send our test payload to our endpoint
    response = tp.client.post(url, payload, content_type="application/json")

    # Was our request valid?
    tp.response_200(response)

Thanks to Will Vincent for advice on and corrections to a draft of this article.