-
Notifications
You must be signed in to change notification settings - Fork 45
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Is it possible to use this with FactoryBoy? #12
Comments
What sorts of problems have you run into so far? I haven't used FactoryBoy before, but from glancing at the If you have some non-MySQL tests that are failing, point them my way and I'll see if I can figure out what's up. Otherwise, I'll check on the progress in #10 and see if I can get it moving again. |
Good point about MySQL... I'll try and find time to knock together a quick project using Postgres and see if I encounter the same issues. This is a summary of an issue I was having with some similar code, and I believe it was the same thing when I switched to trying pytest-flask-sqlalchemy: https://stackoverflow.com/questions/54773484/saving-factoryboy-instances-to-database-with-flask-pytest-and-sqlalchemy But I should verify this again, and rule out MySQL difficulties! |
Thanks for providing some sample code! I still don't have a full grasp on the integration between pytest and FactoryBoy, but this snippet in the SO link you shared looks intriguing: from factory import Sequence
from factory.alchemy import SQLAlchemyModelFactory
from myapp.models import Company
from myapp.models.shared import db
class CompanyFactory(SQLAlchemyModelFactory):
name = Sequence(lambda n: "Company %d" % n)
class Meta:
model = Company
sqlalchemy_session = db.session To attempt an integration with # setup.cfg
[tool:pytest]
mocked-sessions=myapp.models.shared.db.session That should instruct Let me know if that works! If not, the simplified Postgres example will be helpful for reproducing the problem and helping me hack around on it. |
Thanks. I have now got pytest-flask-sqlalchemy working with my app that uses MySQL, although I'm not doing anything particularly advanced, database-wise. The I tried again to get FactoryBoy working, but no joy. It seems it wasn't rolling the database back between tests -- I'd get errors about duplicates for model fields that should be unique from subsequent tests. I'll have to try putting together a minimal example, combining pytest-flask-sqlalchemy and FactoryBoy when I get a chance. |
A reproducible example of your setup would help. We use FactoryBoy with pytest-flask-sqlalchemy + postgres without any troubles. |
I'm not using this library either but I implemented the same pattern myself and so far the only way I got it working was by defining the factory as a pytest fixture too. So I got my @pytest.fixture(scope="function")
def session(db):
....
yield session
.... My user factory: @pytest.fixture
def user_factory(session):
"""Fixture to inject the session dependency."""
class UserFactory(factory.alchemy.SQLAlchemyModelFactory):
"""User model factory."""
class Meta:
model = User
exclude = ("id", "created_at")
sqlalchemy_session = session
sqlalchemy_session_persistence = "commit"
id = factory.Sequence(lambda n: n)
first_name = factory.Faker("first_name")
last_name = factory.Faker("last_name")
email = factory.Faker("email")
password = factory.Faker("password", length=10)
return UserFactory So in a test function you can use it like this: def test_duplicate_email(session, user_factory):
"""Test persisting two users with same email."""
user_factory.create(email="[email protected]")
session.begin_nested()
with pytest.raises(IntegrityError):
user_factory.create(email="[email protected]")
session.rollback()
assert session.query(User).count() == 1 I've tried to make it work with the |
I have the same issue, I investigated a little bit, and I guess the issue is that mocked-sessions patches session after it is bind to Factory, so in this example
So @layoaster solution is only one, that suppose to work, but didn't work for me, I'm getting:
|
Right, I use vanilla FactoryBoy, without the |
I also forgot to mention @citizen-stig that my sample code assumed to be using the vanilla FactoryBoy. Anyways, at first sight, the exception doesn't seem to be related to the session object initialization but rather to the DB model object init. |
Thanks for all this.. @layoaster's solution seems to work well! The only difficulty I've found so far is how to use import factory
@pytest.fixture
def author_factory(db_session):
class AuthorFactory(factory.alchemy.SQLAlchemyModelFactory):
class Meta:
model = Author
sqlalchemy_session = db_session
sqlalchemy_session_persistence = "commit"
name = factory.Faker("name")
return AuthorFactory
@pytest.fixture
def book_factory(db_session):
class BookFactory(factory.alchemy.SQLAlchemyModelFactory):
class Meta:
model = Book
sqlalchemy_session = db_session
sqlalchemy_session_persistence = "commit"
title = factory.Faker("sentence", nb_words=4)
author = factory.SubFactory(author_factory(db_session))
return BookFactory But calling fixtures directly isn't allowed and gets this error:
Maybe there isn't a way with this setup. |
@philgyford when a fixture needs anothers fixture you just pass them via paremeters so your fixture book_factory should look like this: @pytest.fixture
def book_factory(db_session, author_factory):
class BookFactory(factory.alchemy.SQLAlchemyModelFactory):
class Meta:
model = Book
sqlalchemy_session = db_session
sqlalchemy_session_persistence = "commit"
title = factory.Faker("sentence", nb_words=4)
author = factory.SubFactory(author_factory)
return BookFactory |
@layoaster Thank you! I swear, one day my brain will find pytest simple... |
I'm doing something like this: class BaseFactory(factory.alchemy.SQLAlchemyModelFactory):
class Meta:
sqlalchemy_session = scoped_session(
lambda: current_app.extensions["sqlalchemy"].db.session
) Which works... almost... If I try setting:
|
Hello - I'm attempting to use this library with FactoryBoy, and afaict, I've set everything up correctly, but I'm still seeing commits persist beyond each test lifecycle. This is what I've got: [pytest]
mocked-sessions=storage.connection.db.session conftest.py import logging
import os
import pathlib
from typing import TYPE_CHECKING, Type
import pytest
from app import create_app, Config
from .compat import * # noqa: F403,F401
if TYPE_CHECKING:
from models.users import User # noqa: F401
from pytests import factories
logging.captureWarnings(True)
logging.getLogger("faker").setLevel(logging.ERROR)
CUR_DIR = pathlib.Path(__file__).parent.resolve()
AUDIT = CUR_DIR / "audit.db"
DEFAULT = CUR_DIR / "default.db"
os.environ["DISABLE_TRACING"] = "0"
os.environ["DISABLE_JSON_LOGGING"] = "1"
@pytest.fixture(scope="session")
def database(request):
if AUDIT.exists():
AUDIT.unlink()
AUDIT.touch()
if DEFAULT.exists():
DEFAULT.unlink()
DEFAULT.touch()
@pytest.fixture(scope="session")
def app(database, request):
Config.SQLALCHEMY_BINDS.update(
default=f"sqlite:///{str(DEFAULT)}",
replica1=f"sqlite:///{str(DEFAULT)}",
audit=f"sqlite:///{str(AUDIT)}",
)
Config.SQLALCHEMY_DATABASE_URI = Config.SQLALCHEMY_BINDS["default"]
app = create_app()
app.testing = True
app.env = "testing"
with app.app_context():
yield app
@pytest.fixture(scope="session")
def client(app):
with app.test_client() as client:
yield client
@pytest.fixture(scope="session")
def _db(app):
from storage.connection import db
try:
db.create_all(bind=["default", "audit"])
except Exception:
db.session.rollback()
db.create_all(bind=["default", "audit"])
db.session.commit()
yield db
# Automatically enable transactions for all tests, without importing any extra fixtures.
@pytest.fixture(autouse=True)
def enable_transactional_tests(db_session):
pass
@pytest.fixture(scope="function")
def factories(db_session):
from pytests import factories
return factories
@pytest.fixture
def default_user(factories) -> "User":
return factories.DefaultUserFactory.create() factories.py import factory
from factory.alchemy import SQLAlchemyModelFactory
from flask import current_app
from flask_sqlalchemy import get_state
from sqlalchemy.orm import scoped_session
from models.users import User
Session = scoped_session(
lambda: get_state(current_app).db.session,
scopefunc=lambda: get_state(current_app).db.session,
)
class DefaultUserFactory(SQLAlchemyModelFactory):
class Meta:
model = User
sqlalchemy_session = Session
sqlalchemy_session_persistence = "commit"
email = factory.Faker("email")
password = factory.Faker("password")
first_name = factory.Faker("first_name")
last_name = factory.Faker("last_name") test_factories.py import pytest
from models.users import User
from storage.connection import db
def test_session(db_session):
assert db.session is db_session
assert User.query.session is db_session()
@pytest.mark.parametrize(argnames="n", argvalues=range(10))
def test_default_user_factory(default_user: User, n):
assert default_user.id
assert User.query.count() == 1, User.query.all() Now - I can't think of another reason why this would be failing, but I know it's likely something I'm doing. Any thoughts on how I might rectify this? |
@seandstewart did you find a solution? I see it as a consequence of #23 and I kind of embraced it: my factories just start counting at 1 and automatically increases ( |
this worked for me, thanks |
It would be useful to see an example of how to use pytest-flask-sqlalchemy with FactoryBoy/pytest-factoryboy, if they can work together. For example, I'm not clear what would be used for factory classes'
sqlalchemy_session
field.(Background: I use something like pytest-flask-sqlalchemy in my tests, and would like to switch to pytest-flask-sqlalchemy when the MySQL PR is included, and would also like to use FactoryBoy, but have never managed to get the two things to work well together.)
The text was updated successfully, but these errors were encountered: