litestar-org/polyfactory

Factory generates same values for different test through one pytest run (Factory works only once).

Kostiantyn-Salnykov opened this issue · 5 comments

Hi, I've created a Factory for the User (id => pk (unique), email => unique constraint), and provided __async_persistence__ to it.
But It works only once (first time, then all other time Faker generates the same values for id, and email, and maybe for other fields). Tried to fix it with Use declaration, but it helps only with id.

class BaseRawFactory(ModelFactory):
    @classmethod
    def get_mock_value(cls, field_type: Any) -> Any:
        type_name = str(field_type.__name__)
        if type_name == "Email":
            return cls.get_faker().email()

        return super().get_mock_value(field_type)


class BaseFactory(BaseRawFactory):
    created_at: datetime.datetime = PostGenerated(fn=generate_dt)
    updated_at: datetime.datetime = PostGenerated(fn=generate_dt)


class UserFactory(BaseFactory):
    """UserFactory based on Faker and Pydantic."""

    id = Use(fn=uuid.uuid4)  # MY HOTFIX FOR ID
    password_hash: str = PostGenerated(fn=make_password, password=DEFAULT_PASSWORD)

    __model__ = UserCreateToDBSchema
    __allow_none_optionals__ = False  # Factory will generate all fields (even for Optional fields)
    __async_persistence__ = AsyncPersistenceHandler(model=User)

I also used a faker_seed fixture in my conftest.py and faker fixture works as expected inside tests all the time.

@pytest.fixture(scope="function", autouse=True)
def faker_seed() -> int:
    """Generate random seed for Faker instance.

    Returns:
        Random generated integer from 0 up to 100000.
    """
    return random.randint(0, 100000)

It works as expected from IPython console:

await UserFactory.create_async()

It didn't work inside pytest tests ⚠️

(looks like inner __faker__ seed resets to same value on every test function run).
And actually, factory can be used only one time. The first test succeed, and the others - failed. With pytest-randomly this success & failed tests rotating randomly 🤷‍♂️.

My environment:
Python: 3.10
Test dependencies:

[tool.poetry.group.test.dependencies]
pytest = "^7.1.3"
pytest-asyncio = "^0.19.0"
pytest-mock = "^3.8.2"
pytest-sugar = "^0.9.5"
pytest-cov = "^3.0.0"
pytest-randomly = "^3.12.0"
pytest-clarity = "^1.0.1"
Faker = "^15.0.0"
pydantic-factories = "^1.7.0"
pytest-alembic = "^0.8.4"

Possible solutions (thought about it):

  • Clear db after every test - this will slow down all tests;
  • Rollback db session that used inside __async_persistence__ - this is hard to implement without the possibility to connect with pytest fixtures inside it.

Please provide any ideas on how to handle this behavior.

@Kostiantyn-Salnykov could you please post a complete minimal example of model+factory+test so I can run it?

@jtraub it will be hard to extract all necessary source code, but sure that all related staff are located in tests/apps/users/test_models.py
tests/apps/users/factories.py
apps/users/models.py
apps/users/schemas.py
In the case of naming:
models - SQLAlchemy's models
schemas - Pydantic's models

Repo: https://github.com/Kostiantyn-Salnykov/fastapi_quickstart
This is poetry based repo (+ possible to use docker-compose) and exists command make test | make test_cov to run tests.

@Kostiantyn-Salnykov thanks a lot for the code.

I will try to fix the problem in the next few days.

@Kostiantyn-Salnykov the reason of your problem is pytest-randomly resetting random.seed for each test.

So you need to pass --randomly-dont-reset-seed flag if you want to get rid of this behaviour or get rid of pytest-randomly itself.

This command works fine for me poetry run pytest --test-alembic --randomly-dont-reset-seed

P.S. I would also recommend you to provide .env.example file in your repo so user can copy it to .env file and launch your project right away.

As README.md in https://github.com/pytest-dev/pytest-randomly states

...
If faker is installed, its random state is reset at the start of every test. This is also for repeatable fuzzy data in tests - factory boy uses faker for lots of data. This is also done if you're using the faker pytest fixture, by defining the faker_seed fixture (docs).
...

it is the desired (default) behaviour.

So I would suggest to use --randomly-dont-reset-seed flag (so you get random test order from pytest-randomly).

If you need to get rid of randomness everywhere except Faker - that is a different story though.