Using `factory.List` resolves in unusable fixtures
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())
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
Awesome, @skarzi! I would appreciate a fix. Sadly, I am not familiar with the codebase.
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
SubFactorysubclasses withfactory_classset to concrete, but not registered bypytest-factoryboyfactories -
factory.base.ListFactorymodel is set tolistandfactory.base.DictFactoryis set todictso by defaultpytest-factoryboyexpects to findlistanddictfixtures, we can register such fixtures but this will override builtinlistanddictin some places. -
factory.declarations.Listis almost always used withparams(saved indefaultsattribute on instances ofParameteredAttributesubclasses), butparamsaren't supported bypytest-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
paramsinto the codebase and then add some more logic related tofactory.declarations.Listandfactory.declarations.Dictsomewhere inregisterfunction - add totally custom fixtures implementations for
factory.declarations.Listandfactory.declarations.Dictfor 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!
@youtux @hugovk maybe do you have any ideas/insights about this issue and the comment I have added above?
@skarzi did you ever get around to a fix?
Unfortunately not fully :(
It would be great to discuss this issue with some more experienced pytest-factoryboy developers
I just ran into this issue. Is there any idea on how to solve this?