-
Notifications
You must be signed in to change notification settings - Fork 42
Description
I have an existing fixture that will automatically create a test database and return a client attached to that database for a test, and destroy the test database afterwards. I want to use this fixture to build a Django-like model factory, but I'm running into issues with how to inject this fixture's return value into the Factory.
Here's what I've come up with so far:
# factories.py
class FaunaFactory(factory.Factory):
"""This class overrides the default Factory to save the object to Fauna when the
strategy is `create`.
This expects the `Meta.model` of the inheriting class to be set to
`named_model(dict, "name here")`.
"""
class Meta:
abstract = True
# This is expected to be injected via pytest-factoryboy fixture specialization.
fauna_test_client = None
@classmethod
def _create(cls, model_class, *args, **kwargs):
"""This overrides the normal _create so we can instead save the data to Fauna
and return the created document.
"""
data = super()._create(model_class, *args, **kwargs)
# Remove the fauna test client from the data.
fauna_test_client = data.pop("fauna_test_client")
# Use it to store the data in fauna and return the result.
return fauna_test_client.query(
fql(
"""
let collection = Collection(${collection_name})
collection.create(${kwargs})
""",
collection_name=model_class.__name__,
kwargs=data,
)
).data
@classmethod
def _build(cls, model_class, *args, **kwargs):
"""This overrides the normal _build so we can remove the `fauna_test_client`
key/value pair from the data, as this is only used internally when actually
creating the object in fauna.
"""
data = super()._build(model_class, *args, **kwargs)
del data["fauna_test_client"]
return data
# conftest.py
for factory_class in (
AccountFactory,
CustomerFactory,
FalseAlarmFeeScheduleItemFactory,
FalseAlarmSchedulePeriodFactory,
LateFeeScheduleItemFactory,
PermitFeeScheduleItemFactory,
):
register(factory_class, fauna_test_client=LazyFixture("fauna_test_client_with_fsl"))
This is janky, because it only applies the lazy fixture to the model fixture, and not the factory fixture. I attempted to rectify this by overriding the factory fixture like so:
# conftest.py
@pytest.fixture
def fixup_factory_fixture(fauna_test_client_with_fsl):
"""Fixup the provided factory such that it replaces the pytest-factoryboy provided
factory fixture.
Since we haven't found a good way to inject the `fauna_test_client` arg without
having it be an actual argument to the factory, and that the factory fixture doesn't
benefit from the fixture specialization done in the register call, we need to
override each factory fixture to inject the `fauna_test_client_with_fsl` into each
factory and its sub-factories that need the test client.
We use `functools.partial` to inject the test client but leave the result as a
callable so to the user it has the same API as the regular factory fixture.
"""
def _inner(factory_class, fauna_test_client_paths):
return functools.partial(
factory_class,
**{path: fauna_test_client_with_fsl for path in fauna_test_client_paths},
)
return _inner
@pytest.fixture
def account_factory(fixup_factory_fixture):
"""Override the default factory fixture with our fixed up one."""
return fixup_factory_fixture(
factory_class=AccountFactory, fauna_test_client_paths=("fauna_test_client",)
)
However, this simply results in the error TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
when calling model_fixture
due to it assuming the return value of the factory fixture is always the factory class.
I think ultimately, this would be better solved if there was some way to access the pytest request object within the FactoryClass itself. That way, within the overridden _create
classmethod, I could use request.getfixturevalue("fauna_test_client_with_fsl")
to retrieve the test client fixture instead of trying to hack it in as a field value.
I'm not sure if this is possible, since I don't know if the factory fixtures and model fixtures are evaluated on every new test function execution, and thus would have the pytest request fixture available. Does anyone know if this is possible?