pytest-factoryboy icon indicating copy to clipboard operation
pytest-factoryboy copied to clipboard

Using `factory.List` resolves in unusable fixtures

Open sobolevn opened this issue 7 years ago • 7 comments

Setup

Here are my models:

from typing import List

from mypy_extensions import TypedDict


class WakatimeCommit(TypedDict):
    """Class to represent a single Wakatime commit instance from API. """

    hash: str
    author_email: str


class WakatimeCommitsResponse(TypedDict):
    """Class to represent Wakatime response from commits API."""

    commits: List[WakatimeCommit]

And my factories:

import factory
from pytest_factoryboy import register

@register
class WakatimeCommitFactory(factory.BaseDictFactory):
    """
    Fake factory for a single Wakatime commit entry.

    Note:
        This class is marked as protected and is not registered,
        since we do not actually need a single commit in tests.

    """

    class Meta(object):
        model = WakatimeCommit

    hash = '123abc'
    author_email = '[email protected]'


@register
class WakatimeCommitsResponseFactory(factory.BaseDictFactory):
    """Fake factory for Wakatime `/commits` response."""

    class Meta(object):
        model = WakatimeCommitsResponse

    commits = factory.List([
        factory.SubFactory(WakatimeCommitFactory) for _ in range(10)
    ])

And finally my test:

def test_pipeline_for_opened_valid_mr(
    wakatime_commits_response,
):
    """
    Tests the whole process of adding spent time to a merge request.

    All requests are mocked, everything returns valid responses.
    """
    print(wakatime_commits_response)

    assert 1 == 2

Error

This tests fails due to unresolved fixtures:

def test_pipeline_for_opened_valid_mr(
file <string>, line 2: source code not available
file <string>, line 2: source code not available
E       fixture 'list' not found
>       available fixtures:
...
wakatime_commit, wakatime_commit__author_email, wakatime_commit__hash, wakatime_commit__total_seconds, wakatime_commit_factory, wakatime_commits_response, wakatime_commits_response__commits, wakatime_commits_response_factory
...

I guess this happens due to the fact that I am using factory.List.

Workaround

class WakatimeCommitsResponseFactory(factory.BaseDictFactory):
    ...

    @factory.post_generation
    def commits(self, create, extracted, **kwargs):
        if extracted:
            self['commits'] = extracted
        else:
            self['commits'] = []
            for _ in range(10):
                self['commits'].append(WakatimeCommitFactory.build())

sobolevn avatar Jul 21 '18 08:07 sobolevn

I can take this issue as well as https://github.com/pytest-dev/pytest-factoryboy/issues/65, because it affected me several times. Could someone give me some directions firstly? I am still looking for the clearest solution for that

skarzi avatar Jun 30 '19 15:06 skarzi

Awesome, @skarzi! I would appreciate a fix. Sadly, I am not familiar with the codebase.

sobolevn avatar Jun 30 '19 19:06 sobolevn

Currently it's really hard to implement factory.declarations.List and factory.declarations.Dict support in a proper way, because of few reasons:

  • both classes are SubFactory subclasses with factory_class set to concrete, but not registered by pytest-factoryboy factories
  • factory.base.ListFactory model is set to list and factory.base.DictFactory is set to dict so by default pytest-factoryboy expects to find list and dict fixtures, we can register such fixtures but this will override builtin list and dict in some places.
  • factory.declarations.List is almost always used with params (saved in defaults attribute on instances of ParameteredAttribute subclasses), but params aren't supported by pytest-factoryboy - this is connected with following issues https://github.com/pytest-dev/pytest-factoryboy/issues/40, https://github.com/pytest-dev/pytest-factoryboy/issues/90 - but in my opinion following code is the simplest example of this issue:
from dataclasses import dataclass 

import factory
from pytest_factoryboy import LazyFixture, register


@dataclass
class Author:
    full_name: str


@dataclass
class Book:
    author: Author
    title: str


class AuthorFactory(factory.Factory):
    full_name = factory.Sequence(lambda counter: f'Author {counter}')

    class Meta:
        model = Author


class LemonySnicketBookFactory(factory.Factory):
    author = factory.SubFactory(AuthorFactory, full_name='Lemony Snicket')
    title = factory.Sequence(lambda counter: f'A Series of Unfortunate Events {counter}')

    class Meta:
        model = Book


register(AuthorFactory)
register(LemonySnicketBookFactory, _name='lemony_snicket_book')


def test_subfactory_defaults(lemony_snicket_book):
    """Failure because ``lemony_snicket_book.author.full_name`` is ``'Author 1'``"""
    assert lemony_snicket_book.author.full_name == 'Lemony Snicket'

So with my current knowledge, I see 2 possible solutions:

  • firstly we need to add some support of params into the codebase and then add some more logic related to factory.declarations.List and factory.declarations.Dict somewhere in register function
  • add totally custom fixtures implementations for factory.declarations.List and factory.declarations.Dict for instance, generate unique fixture for every instance of these classes

I am still not so much into pytest-factoryboy and factoryboy codebase, because it's really dynamic and sometimes hard to debug code, so any responses with feedback or any advice is really welcome!

skarzi avatar Aug 01 '20 10:08 skarzi

@youtux @hugovk maybe do you have any ideas/insights about this issue and the comment I have added above?

skarzi avatar Dec 28 '20 11:12 skarzi

@skarzi did you ever get around to a fix?

lovetoburnswhen avatar Oct 19 '21 21:10 lovetoburnswhen

Unfortunately not fully :(

It would be great to discuss this issue with some more experienced pytest-factoryboy developers

skarzi avatar Oct 27 '21 12:10 skarzi

I just ran into this issue. Is there any idea on how to solve this?

ryancausey avatar Feb 27 '24 05:02 ryancausey