Mock Django models using Faker and Factory Boy
Tags: django, python, testing, programming
Published: 2021-11-18 18:28:47

This post cross-published with [OnePublish](https://www.onepubli.sh)

In this post, you'll learn how real-world apps are mocking Django models by using Factory Boy. Before we start, make sure you have a basic understanding of TDD (Test-Driven Development). I will show you the simple working logic of mocking Django models and provide some key takeaways where you can easily apply them to your project.

##Installation and Basic Usage
Install factory boy package by the following command:

```powershell
pip install factory-boy
```

In a nutshell, factory boy will create a mocked instance of your class based on the values that you'll provide. Why even do we need this? Assume that you have a blog app where you want to test the posts and comments. You are going to need real examples of datasets to test functionalities more accurately. So instead of creating the data manually or by using fixtures, you can easily generate instances of a particular model by using factory boy.

>The purpose of `factory_boy` is to provide a default way of getting a new instance, while still being able to override some fields on a per-call basis.

Now let's see how to mock `dataclass` in python. Create an empty directory named `data` and also add `__init__.py`  file inside to mark it as a python package. We'll follow the example above which 
is going to generate the mocked `Post` instances.

**data/models.py**

```python
from dataclasses import dataclass

@dataclass
class Post:
    title: str
    description: str
    published: bool
    image: str
```

Quick reminder about `dataclass`:

>`dataclass` module is introduced in Python 3.7 as a utility tool to make structured classes specially for storing data - geeksforgeeks.org

We created a `dataclass` which holds four attributes. Now, this class needs a factory where we can create mocked instances.

**data/factories.py**

```python
import factory
import factory.fuzzy

from data.models import Post

class PostFactory(factory.Factory):

    class Meta: 
        model = Post
    
    title = factory.Faker("name")
    description = factory.Faker("sentence")
    published = factory.fuzzy.FuzzyChoice(choices=[True, True, True, False])
    image = factory.Faker("image_url")
```

In Django, you'll need to inherit from `factory.django.DjangoModelFactory` class instead of just `factory.Factory`.

We are setting `model = Post` that defines the particular class we are going to mock.

The package has a built-in fake data generator class named `FuzzyAttributes`. However, in newer versions of `factory_boy`, they also included `Faker` class where you can use it to generate fake data. So, we used a few of them to mock our fields with fake data.

Now, it's time to put all these together:

**main.py**

```python
from data.factories import PostFactory

posts = PostFactory.create_batch(10)

for post in posts:
    print(post)
```

The first argument of `create_batch` function takes a number (size) of generated items and allows override the factory class attributes.

```powershell
python main.py
```

**Output:**

```python
Post(title='Tracy Hernandez', description='Similar house wind bit win anything process even.', published=True, image='https://placekitten.com/209/389')
Post(title='Kimberly Henderson', description='Behavior wife phone agency door.', published=True, image='https://www.lorempixel.com/657/674')
Post(title='Jasmine Williams', description='Action experience cut loss challenge.', published=True, image='https://placekitten.com/365/489')
Post(title='Nicholas Moody', description='Consumer language approach risk event lose.', published=True, image='https://placekitten.com/756/397')
Post(title='Dr. Curtis Monroe', description='Firm member full.', published=True, image='https://dummyimage.com/238x706')
Post(title='David Martin', description='Join fall than.', published=False, image='https://dummyimage.com/482x305')
Post(title='Seth Oliver', description='Including most join resource heavy.', published=True, image='https://www.lorempixel.com/497/620')
Post(title='Daniel Berger', description='Summer mean figure husband read.', published=True, image='https://dummyimage.com/959x180')
Post(title='Samantha Romero', description='Window leader subject defense lawyer.', published=False, image='https://placeimg.com/965/518/any')
Post(title='Jessica Carroll', description='Would try religious opportunity future blood our.', published=True, image='https://placekitten.com/911/434')
```
Once you run the program, the output should look above, which means the factory successfully generated instances from our model class.

##Advanced Usage in Django
Try to mock each model with `factory_boy` that you're going to test rather than create thousands of fixtures ( JSON files that hold dummy data ). Assuming that, in future, you'll have critical changes in your models where all these JSON objects must be refactored to fit the current state.

The logic works same for Django as well. But before applying it to your project, let me share the best practices and use cases with you.

Hold your `factories.py` inside the `tests` directory for each app.

##Override Factory Boy Attributes

You can set some attributes as `None` if they don't require initial values:

```python
import factory
import factory.django

from blog.models import Post


class PostFactory(factory.django.DjangoModelFactory):
    title = factory.Faker('name')
    image_url = factory.Faker('image_url')
    tags = None

    class Meta:
        model = Post
```

 then override them later:

```python
PostFactory(
	tags=['spacex', 'tesla']
)
```

Note that, `create_batch` also receives `kwargs` where it allows overriding attributes:

```python
PostFactory.create_batch(
	5, #number of instances
	tags=['spacex', 'tesla'] #override attr
)
```

##Helper Methods and Hooks
###post_generation

Assume that `Comment` model has a field `post` as a foreign key where it built from `Post` object. At this point, we can use `post_generation` hook to assign created `post` instance to `CommentFactory`. Don't confuse the name conventions here:

`post_generation` is a built-in decorator that allows doing stuff with generated factory instance.

`post` object is an instance of `PostFactory` that we are using as an example.

```python
import factory
import factory.fuzzy

from blog.models import Post, Comment


class CommentFactory(factory.django.DjangoModelFactory):

    class Meta:
        model = Comment
    
    post = None
    message = factory.Faker("sentence")


class PostFactory(factory.Factory):

    class Meta: 
        model = Post
    
    title = factory.Faker("name")
    description = factory.Faker("sentence")
    published = factory.fuzzy.FuzzyChoice(choices=[True, True, True, False])
    image = factory.Faker("image_url")
    

    @factory.post_generation
    def post_related_models(obj, create, extracted, **kwargs):
        if not create:
            return
        CommentFactory.create(
            post=obj
        )
```

You can change the name of the function whatever you want.

- `obj` is the `Post` object previously generated
- `create` is a boolean indicating which strategy was used
- `extracted` is `None` unless a value was passed in for the 
- `PostGeneration` declaration at `Factory` declaration time
- `kwargs` are any extra parameters passed as `attr__key=value` when calling the `Factory`

###lazy_attribute

Now, let's say you have a field name `slug` where you can't use upper case or any hyphens. There is a decorator named `@factory.lazy_attribute` which kind of behaves like `lambda` :

```python
from django.utils.text import slugify

class PostFactory(factory.django.DjangoModelFactory):

    class Meta: 
        model = Post
    
    title = factory.Faker("name")
    description = factory.Faker("sentence")
    published = factory.fuzzy.FuzzyChoice(choices=[True, True, True, False])
    image = factory.Faker("image_url")
    
    @factory.lazy_attribute
    def slug(self):
        return slugify(self.title)
```
The name of the method will be used as the name of the attribute to fill with the return value of the method. So, you can access slug value simply like `instance.slug`

###_adjust_kwargs

There are also other helper methods are available such as adjusting `kwargs` of instance. Assume that you need to concatenate post title with `_example` string:

```python
class PostFactory(factory.Factory):

    class Meta: 
        model = Post
    
    title = factory.Faker("name")
    description = factory.Faker("sentence")
    published = factory.fuzzy.FuzzyChoice(choices=[True, True, True, False])
    image = factory.Faker("image_url")
    

    @classmethod
    def _adjust_kwargs(cls, **kwargs):
        kwargs['title'] = f"{kwargs['title']}_example"
        return kwargs
```

Actually, the task above might be achieved by using `factory.lazy_attribute` but in some cases, you'll need access to the instance attributes after its generated but not while generation.

#For Video Explanation:

{% youtube 5PLuIOf1Ehw %}

##Support 🌏

If you feel like you unlocked new skills, please share them with your friends and subscribe to the youtube channel to not miss any valuable information.