DEV Community

Lina Rudashevski
Lina Rudashevski

Posted on

Writing Tests with pytest and pytest-factoryboy

Testing in python can be tricky so I've outlined what works for me. I like to use pytest with pytest-factoryboy to generate testing data. pytest-factoryboy is an easier way to test than having to maintain fixtures in a test database.

Define the factories. They look very similar to models.

# tests/ 

import factory
from faker import Factory as FakerFactory

from django.contrib.auth.models import User
from django.utils.timezone import now

from app.models import Game, Message, GamePlayer, Round, Move

faker = FakerFactory.create()

class UserFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = User

    email = factory.Faker("safe_email")
    username = factory.LazyAttribute(lambda x:

    def _prepare(cls, create, **kwargs):
        password = kwargs.pop("password", None)
        user = super(UserFactory, cls)._prepare(create, **kwargs)
        if password:
            if create:
        return user

class GameFactory(factory.django.DjangoModelFactory):
    room_name = factory.LazyAttribute(lambda x:
    game_status = "active"
    created_at = factory.LazyAttribute(lambda x: now())
    updated_at = factory.LazyAttribute(lambda x: now())
    round_started = False
    is_joinable = True

    class Meta:
        model = Game

class GamePlayerFactory(factory.django.DjangoModelFactory):
    followers = 0
    selfies = 3
    user = factory.SubFactory(UserFactory)
    started = False
    game = factory.SubFactory(GameFactory)

    class Meta:
        model = GamePlayer

class MessageFactory(factory.django.DjangoModelFactory):
    game = factory.SubFactory(GameFactory)
    username = None
    message = factory.LazyAttribute(lambda x: faker.sentence())
    created_at = factory.LazyAttribute(lambda x: now())
    message_type = None

    class Meta:
        model = Message

# etc 

In a file I register the factories.

Fixtures defined in file will be recognized in any test file. "Registering" the factories lets you inject them into your tests with a lowercase-underscore class name. In this same file, I'm defining fixtures with @pytest.fixture() which I'll use in my tests, and injecting them with the factories I just registered.

# tests/ 
import pytest
from pytest_factoryboy import register

from .factories import (


def game(game_factory):
    return game_factory()

def rnd(game, round_factory):
    return round_factory(game=game, started=True)

def p_1(game, user_factory, game_player_factory):
    return game_player_factory(game=game, user=user_factory(), started=True)

#etc...more player fixtures defined

Write tests

Here I'm writing tests. I'm passing in my fixture data into my test method, and also passing in a factory called move because that's what I'm mainly testing here. In order to test player moves, I first needed to have created the players, the game, and the round, which is what I did in my file. I only need to do this once, and I don't need to import the fixtures because anything declared in is visible to the test files.

# tests/

import pytest

from import (
from import round_service, message_service
from app.models import Move, Message, GamePlayer

def test_iphone_go_live_do_selfie(rnd, p_1, p_2, p_3, move_factory):
    """if a girl calls a girl who is trying to take a selfie, she sustains go
    live damage"""
    move_factory(round=rnd, action_type=GO_LIVE, player=p_1, victim=None)
    move_factory(round=rnd, action_type=CALL_IPHONE, player=p_2, victim=p_3)
    move_factory(round=rnd, action_type=POST_SELFIE, player=p_3, victim=None)
    tab = RoundTabulation(rnd).tabulate()
    assert tab[] == 0
    assert tab[] == GO_LIVE_DM
    assert tab[] == GO_LIVE_DM

def test_go_live_with_call(rnd, p_1, p_2, p_3, p_4, p_5, move_factory):
    """message was created, points are corrected, player is removed from player
    points array, they have one less story"""
    move_factory(round=rnd, action_type=GO_LIVE, player=p_1, victim=None)
    move = move_factory(round=rnd, action_type=CALL_IPHONE, player=p_2, victim=p_1)
    move_factory(round=rnd, action_type=DISLIKE, player=p_3, victim=p_1)
    move_factory(round=rnd, action_type=DISLIKE, player=p_4, victim=p_1)
    move_factory(round=rnd, action_type=LEAVE_COMMENT, player=p_5, victim=p_1)
    tab = RoundTabulation(rnd).tabulate()
    assert message(, p_2.user.username) in message_service.iphone_msg(
        move, p_1.user.username
    p_1 = GamePlayer.objects.get(
    assert p_1.go_live == 1
    assert tab[] == -50

Execute the tests

Now your tests can run with test data!

[17:59:32] (master) selfies
🙋 docker exec -it 5c93db3f3183 bash
root@5c93db3f3183:/selfies# pytest app/tests/
======================================= test session starts ========================================
platform linux -- Python 3.7.4, pytest-5.0.1, py-1.8.0, pluggy-0.12.0
Django settings: selfies.settings (from ini file)
rootdir: /selfies, inifile: pytest.ini
plugins: factoryboy-2.0.3, django-3.5.1
collected 15 items                                                                                 

app/tests/ ...............

Top comments (0)