Model Bakery

repository·main·Indexed 21 days ago

https://github.com/model-bakers/model_bakery

A smart object creation facility for Django testing that provides a simple API to instantiate models with automatically generated data. It includes tools like baker.make() for persisted instances and baker.prepare() for non-persisted instances, reducing boilerplate for test setup. Features include automatic relationship handling, support for sequences, bulk creation, custom field generators, and options to override default field generation behavior.

Tokens
12.4K
Snippets
43
Records
56
Agent score
77%

What's inside model-bakery

  1. Overview of Model Bakery features

    main

    Model Bakery is a tool for creating Django model fixtures for testing. Key capabilities include:

    • Automatic Data Generation: Automatically generates test data for Django models.
    • Sensible Defaults: Provides default values for all supported field types.
    • Customization: Supports custom field overrides and "recipes" for reusable object patterns.
    • Relationship Management: Handles related models and complex database relationships automatically.
  2. What is a Recipe and how to use it

    main

    A Recipe is a set of rules used to generate data for your models, allowing you to improve the semantics of generated data or avoid manual setup for complex objects. You can define recipes locally in your test modules or store them in a central module (e.g., baker_recipes.py) to be reused across your project.

    To use a stored recipe, use baker.make_recipe('path.to.recipe') to persist it to the database, or baker.prepare_recipe('path.to.recipe') to create an unpersisted instance.

    from model_bakery.recipe import Recipe
    from shop.models import Customer
    
    # Define a recipe
    customer_joe = Recipe(
        Customer,
        name='John Doe',
        nickname='joe',
        age=18,
        birthday=date.today(),
        last_shopping=datetime.now()
    )
  3. Handle Model Relationships (ForeignKey and M2M)

    main

    Model Bakery automatically handles relationships when creating instances.

    • ForeignKey and OneToOneFields: Model Bakery automatically creates and persists the related object.
    • ManyToMany (M2M) Relationships: By default, M2M relationships are not populated. To automatically create related instances for M2M fields, pass make_m2m=True.
    • Explicit M2M: You can prepare a specific set of related objects and pass them directly to the make call.
    • Field Lookups: You can set values on related objects using Django-style double-underscore syntax (e.g., customer__name='Bob').
    from model_bakery import baker
    
    # Automatic ForeignKey creation
    history = baker.make('shop.PurchaseHistory')
    
    # Automatic M2M creation (must be explicitly enabled)
    history_with_m2m = baker.make('shop.PurchaseHistory', make_m2m=True)
    
    # Explicitly passing a prepared set of M2M objects
    products_set = baker.prepare(Product, _quantity=5)
    history_explicit = baker.make(PurchaseHistory, products=products_set)
    
    # Using field lookups for related objects
    bob_history = baker.make('shop.PurchaseHistory', customer__name='Bob')
  4. Understand Model Bakery's default field generation behavior

    main

    By default, Model Bakery follows these rules when generating model instances:

    1. Skips optional fields: Fields with null=True or blank=True are skipped (left empty).
    2. Uses defaults: If a field has a default value defined in the model, that value is used.

    When to manually set values: You should manually provide values for fields that:

    • Have special validation logic.
    • Require uniqueness constraints that random generation might violate.
    • Are critical to the specific logic of your test.

    When to let Baker handle it: Let the Baker handle fields that:

    • Do not matter for the specific test case.
    • Do not require special validation.
    • Are required to create the object (non-nullable/non-blank).
  5. Use `_bulk_create` for performance

    main

    To speed up the creation of many instances, use the _bulk_create=True parameter. This uses Django's bulk_create method instead of calling .save() on every instance.

    Warning: Django's bulk_create does not update the primary key on the created objects. Consequently, Model Bakery still needs to call save() for all foreign key objects to ensure they exist. If you are creating many instances with foreign keys, you may need to perform individual bulk creations per foreign key to optimize queries.

    from model_bakery import baker
    
    # Bulk create instances
    users = baker.prepare(User, _quantity=5, _bulk_create=True)
    
    # Optimized pattern for bulk creating related objects
    baker.prepare(User, _quantity=5, _bulk_create=True)
    user_iter = User.objects.all().iterator()
    baker.prepare(Profile, user=user_iter, _quantity=5, _bulk_create=True)
  6. Set explicit field values and sequences

    main

    While Model Bakery uses random values by default, you can override them using several methods:

    • Direct Values: Pass the field name and value as a keyword argument.
    • Callables: Pass a function to be called for each instance.
    • Iterables: Pass an iterable (like a list or a itertools.cycle) to provide values sequentially.
    • Sequences: Use baker.seq(prefix) to generate unique, incrementing values (e.g., Joe1, Joe2) to avoid uniqueness validation errors.
    import itertools
    import random
    from model_bakery import baker
    
    # Direct value
    customer = baker.make('shop.Customer', age=21)
    
    # Using a callable
    def get_random_name():
        return random.choice(["Name1", "Name2"])
    customer = baker.make('shop.Customer', name=get_random_name)
    
    # Using an iterable
    names = ("Onkar", "Pruthviraj", "Shubham")
    customer = baker.make('shop.Customer', name=itertools.cycle(names))
    
    # Using a sequence for unique values
    customer = baker.make('shop.Customer', name=baker.seq('Joe'))
  7. Create persisted model instances with `baker.make()`

    main

    Use baker.make() to create and save model instances to the database. You can pass the model class directly or use a string identifier.

    Model Identification Forms:

    • app_label.model_name: Use this form (e.g., 'shop.Customer') if you have multiple models with the same name in different apps.
    • model_name: Use this form (e.g., 'Product') if the model name is unique across your project. This form is case-insensitive.

    To create multiple instances at once, use the _quantity parameter.

    from model_bakery import baker
    from shop.models import Customer
    
    # Using the class directly
    customer = baker.make(Customer)
    
    # Using the app_label.model_name string form
    customer = baker.make('shop.Customer')
    
    # Using the model_name string form (if unique)
    product = baker.make('Product')
    
    # Creating multiple instances
    customers = baker.make('shop.Customer', _quantity=3)
  8. Create non-persisted model instances with `baker.prepare()`

    main

    Use baker.prepare() when you need model instances for testing but do not want them saved to the database. This works similarly to make(), but neither the primary instance nor its related instances are persisted.

    Important Considerations:

    • Reverse Relationships: Accessing reverse foreign key relationships on unsaved instances (via prepare()) raises a ValueError. To avoid this, use baker.make() or use _save_related=True to save related FK instances while keeping the main instance unsaved.
    • GenericForeignKey: When using prepare() with a GenericForeignKey, the content_object attribute will not be accessible because it requires database access. You can still access content_type.app_label, content_type.model, and object_id.
    from model_bakery import baker
    
    # Creates unsaved instances
    customer = baker.prepare('shop.Customer')
    
    # Creates unsaved main instance, but saves related FK instances
    history = baker.prepare('shop.PurchaseHistory', _save_related=True)
    assert history.id is None
    assert bool(history.customer.id) is True
  9. Use Model Bakery with Django TestCase

    main

    When using the standard Django TestCase runner, you can use Model Bakery within the setUp method to prepare model instances for your tests. This ensures that every test method in the class has access to a freshly baked instance via self.

    # Core Django imports
    from django.test import TestCase
    
    # Third-party app imports
    from model_bakery import baker
    
    from shop.models import Customer
    
    class CustomerTestModel(TestCase):
        """
        Class to test the model Customer
        """
    
        def setUp(self):
            """Set up test class."""
            self.customer = baker.make(Customer)
    
        def test_using_customer(self):
            """Test function using baked model."""
            self.assertIsInstance(self.customer, Customer)
  10. Use Model Bakery with pytest

    main

    When using pytest (typically with the pytest-django plugin), the recommended pattern is to wrap baker.make() calls inside a @pytest.fixture. This allows you to inject the baked model directly into your test functions as an argument.

    # pytest import
    import pytest
    
    # Third-party app imports
    from model_bakery import baker
    
    from shop.models import Customer
    
    @pytest.fixture
    def customer():
        """Fixture for baked Customer model."""
        return baker.make(Customer)
    
    def test_using_customer(customer):
        """Test function using fixture of baked model."""
        assert isinstance(customer, Customer)
  11. Override default field generation with explicit values or `_fill_optional`

    main

    You can control which fields are populated with random data using three methods:

    1. Explicitly defining values: Pass keyword arguments directly to baker.make().
    2. Filling specific optional fields: Use the _fill_optional parameter with a list of field names to populate only those specific optional fields with random data.
    3. Filling all optional fields: Pass _fill_optional=True to populate every field that is marked as optional (null=True or blank=True) with random data.
    from model_bakery import baker
    
    # 1. Explicitly defining values
    customer = baker.make('shop.Customer', enjoy_jards_macale=True, bio="A fan of Jards Malacé")
    
    # 2. Passing a list of specific fields to fill
    customer = baker.make('shop.Customer', _fill_optional=['enjoy_jards_macale', 'bio'])
    
    # 3. Filling all optional fields
    customer = baker.make('shop.Customer', _fill_optional=True)
    from model_bakery import baker
    
    customer = baker.make('shop.Customer', enjoy_jards_macale=True, bio="A fan of Jards Malacé")
    
    customer = baker.make('shop.Customer', _fill_optional=['enjoy_jards_macale', 'bio'])
    
    customer = baker.make('shop.Customer', _fill_optional=True)