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.
# tests/factories.py 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: faker.name()) @classmethod def _prepare(cls, create, **kwargs): password = kwargs.pop("password", None) user = super(UserFactory, cls)._prepare(create, **kwargs) if password: user.set_password(password) if create: user.save() return user class GameFactory(factory.django.DjangoModelFactory): room_name = factory.LazyAttribute(lambda x: faker.name()) 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
Fixtures defined in
conftest.py 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/conftest.py import pytest from pytest_factoryboy import register from .factories import ( UserFactory, GameFactory, GamePlayerFactory, MoveFactory, RoundFactory, ) register(UserFactory) register(GameFactory) register(GamePlayerFactory) register(MoveFactory) register(RoundFactory) @pytest.fixture() def game(game_factory): return game_factory() @pytest.fixture() def rnd(game, round_factory): return round_factory(game=game, started=True) @pytest.fixture() 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
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
conftest.py file. I only need to do this once, and I don't need to import the fixtures because anything declared in
conftest.py is visible to the test files.
# tests/test_round_service.py import pytest from app.services.round_service import ( RoundTabulation, LEAVE_COMMENT, CALL_IPHONE, DISLIKE, GO_LIVE, POST_SELFIE, DONT_POST, NO_MOVE, DISLIKE_DM, POST_SELFIE_PTS, POST_SELFIE_DM, GO_LIVE_DM, NO_MOVE_DM, ) from app.services import round_service, message_service from app.models import Move, Message, GamePlayer @pytest.mark.django_db 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[p_1.id] == 0 assert tab[p_2.id] == GO_LIVE_DM assert tab[p_3.id] == GO_LIVE_DM @pytest.mark.django_db 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(rnd.game, p_2.user.username) in message_service.iphone_msg( move, p_1.user.username ) p_1 = GamePlayer.objects.get(id=p_1.id) assert p_1.go_live == 1 assert tab[p_1.id] == -50
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_round_service.py ======================================= 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/test_round_service.py ...............