Introduction

Automatic testing of services which involves database access gets quite quickly cumbersome, especially the preparation of the database. By the time the system matures, complex scenarios need to be covered. A predefined test scenario helps with the setup, but is not very flexible. On the search for a solution quite quickly factory_girl appeared on the radar. But nothing similar could be found for Python.

With this library quick, obvious and easily understandable test scenarios can be created which are flexible, easily to maintain and to extend.

Word of truth must be spoken: it only works with SQLAlchemy’s ORM mapper! (see doc)

Installation

The library is hosted on PyPI and can be installed via

pip install sqlalchemy-fixture-factory

Quick Example

Assume following ORM definitions:

class Account(Base):
    __tablename__ = 'account'

    id = Column(Integer, primary_key=True)
    name = Column('name', Unicode)

class Person(Base):
    __tablename__ = 'person'

    id = Column(Integer, primary_key=True)
    first_name = Column('first_name', Unicode)
    account_id = Column(Integer, ForeignKey('account.id'))
    account = relationship(Account)

definition of a fixture for a person. It includes an account:

class ArnoldAccount(BaseFix):
    MODEL = Account
    name = "arney"

class ArnoldPerson(BaseFix):
    MODEL = Person
    name = "Arnold"
    account = sqla_fix_fact.subFactoryModel(ArnoldAccount)

now the usage (I assume SQLAlchemy is properly initialized):

# initialize the fixture factory
fix_fact = SqlaFixFact(db_session)

# create a fxiture
arnold_fix = ArnoldPerson(fix_fact).create()

# or more of them
while i in xrange(3):
    arnold_fix = ArnoldPerson(fix_fact).create()

# test
assert 4 == db_session.query(Person).count()

Table of Contents

Instantiating Fixtures

For for the following examples, assume this fixture

class ArnoldPerson(BaseFix):
    MODEL = Person

    first_name = "Arnold"
    family_name = "Schwarz"

First you need to instantiate the fixture class and pass the SqlaFixFact instance as fist parameter:

fixture = ArnoldPerson(fix_fact)

You can add additional kwargs to alter the default values. For more details see Substitution of Values

To get a SQLAlchemy ORM model instance there are 3 ways

create()

Adds this model to the session. This instance is not registered and thus can never be referred to via BaseFix.get(). Thus each call to create() adds a new instance of the fixture to the DB.

arnold_fix_1 = ArnoldPerson(fix_fact).create()
arnold_fix_2 = ArnoldPerson(fix_fact).create()
arnold_fix_3 = ArnoldPerson(fix_fact).create()

db_session.query(Person).count()  # is 3

get()

Returns an already existing model instance, or creates one and registers it to be able to find it later and then returns the instance.

Note: Changed properties via the kwargs parameter are recognized and result in a new instance.

arnold_fix_1 = ArnoldPerson(fix_fact).get()
arnold_fix_2 = ArnoldPerson(fix_fact).get()
arnold_fix_3 = ArnoldPerson(fix_fact).get()

db_session.query(Person).count()  # is 1

model()

Returns a model instance of this fixture which is ready to be added. The model itself is not added to the DB but all dependencies are.

arnold_fix = ArnoldPerson(fix_fact).model()

db_session.query(Person).count()  # is 0

db_session.add(arnold_fix)
db_session.query(Person).count()  # is 1

Referencing other Fixtures

To build complete scenarios, referencing other fixtures becomes necessary.

To reference other fixtures, use following functions:

These function behave the same as the methods of the BaseFix class.

class ArnoldAccount(BaseFix):
    MODEL = Account
    name = "arney"

class ArnoldPerson(BaseFix):
    MODEL = Person
    name = "Arnold"
    account = sqla_fix_fact.subFactoryModel(ArnoldAccount)

Prefer the usage of subFactoryModel() for referencing other fixtures.

You can also add kwargs to alter properties of the factory as described in Substitution of Values

class ArnoldAccount(BaseFix):
    MODEL = Account
    name = "arney"

class ArnoldPerson(BaseFix):
    MODEL = Person
    name = "Arnold"
    account = sqla_fix_fact.subFactoryModel(ArnoldAccount)

class ArnoldPersonAdmin(BaseFix):
    MODEL = Person
    name = "Arnold"
    account = sqla_fix_fact.subFactoryModel(ArnoldAccount, name="arney-admin")

Do not use the substitutions extensively at referencing other fixtures. In most cases an own fixture should be defined. Like in the example from above.

class ArnoldAccount(BaseFix):
    MODEL = Account
    name = "arney"

class ArnoldPerson(BaseFix):
    MODEL = Person
    name = "Arnold"
    account = sqla_fix_fact.subFactoryModel(ArnoldAccount)

class ArnoldAdminAccount(BaseFix):
    MODEL = Account
    name = "arney-admin"

class ArnoldPersonAdmin(BaseFix):
    MODEL = Person
    name = "Arnold"
    account = sqla_fix_fact.subFactoryModel(ArnoldAdminAccount)

To save you the burden of copying to much information, you could of course inherit from other fixtures. This inherits all properties except those you overwrite.

class ArnoldAccount(BaseFix):
    MODEL = Account
    name = "arney"

class ArnoldPerson(BaseFix):
    MODEL = Person
    name = "Arnold"
    account = sqla_fix_fact.subFactoryModel(ArnoldAccount)

class ArnoldAdminAccount(ArnoldAccount):
    name = "arney-admin"

class ArnoldPersonAdmin(ArnoldPerson):
    account = sqla_fix_fact.subFactoryModel(ArnoldAdminAccount)

You can also add sub-factories in lists

class ViewRole(BaseFix):
    MODEL = Role
    name = "View Role"

class EditRole(BaseFix):
    MODEL = Role
    name = "Edit Role"

class ArnoldAccount(BaseFix):
    MODEL = Account
    name = "arney"
    # Use get to reference to the roles, as only one instance in the DB is desired
    roles = [sqla_fix_fact.subFactoryGet(ViewRole), sqla_fix_fact.subFactoryGet(EditRole)]

class ArnoldPerson(BaseFix):
    MODEL = Person
    name = "Arnold"
    account = sqla_fix_fact.subFactoryModel(ArnoldAccount)

Substitution of Values

To generate a slightly different fixture than a defined one, does not need a new fixture definition. It is possible to alter properties on instantiation time.

Assume following model and fixture for this page

class Account(Base):
    __tablename__ = 'account'

    id = Column(Integer, primary_key=True)
    name = Column('name', Unicode)

class Person(Base):
    __tablename__ = 'person'

    id = Column(Integer, primary_key=True)
    first_name = Column('first_name', Unicode)
    family_name = Column('first_name', Unicode)
    account_id = Column(Integer, ForeignKey('account.id'))
    account = relationship(Account)

# Fixtures
class ArnoldAccount(BaseFix):
    MODEL = Account
    name = "arney"

class ArnoldAdminAccount(BaseFix):
    MODEL = Account
    name = "arney-admin"

class ArnoldPerson(BaseFix):
    MODEL = Person

    first_name = "Arnold"
    family_name = "Schwarz"
    account = sqla_fix_fact.subFactoryModel(ArnoldAccount)

To substitute a property simply add a key word argument to the constructor

arnold_fix = ArnoldPerson(fix_fact, first_name="Franz").create()

assert arnold_fix.first_name == "Franz"

Of course more than one substitution is possible

arnold_fix = ArnoldPerson(fix_fact, first_name="Franz", family_name="Egger").create()

assert arnold_fix.first_name == "Franz"
assert arnold_fix.family_name == "Egger"

Even references to other factories can be substituted. In this case you need to use one of the sub-factory definition functions!

arnold_fix = ArnoldPerson(fix_fact, account=sqla_fix_fact.subFactoryModel(ArnoldAdminAccount)).create()

assert arnold_fix.account.name == "arney-admin"

API

class sqla_fix_fact.SqlaFixFact(db_session)

Fixture factory manager

sqla_fix_fact.subFactoryGet(fixture, **kwargs)

To be used in fixture definition (or in the kwargs of the fixture constructor) to reference a other fixture using the BaseFix.get() method.

Parameters:
  • fixture – Desired fixture
  • kwargsOptional: key words to overwrite properties of this fixture
Returns:

Proxy object for the desired fixture including the altered properties

sqla_fix_fact.subFactoryCreate(fixture, **kwargs)

To be used in fixture definition (or in the kwargs of the fixture constructor) to reference a other fixture using the BaseFix.create() method.

Parameters:
  • fixture – Desired fixture
  • kwargsOptional: key words to overwrite properties of this fixture
Returns:

Proxy object for the desired fixture including the altered properties

sqla_fix_fact.subFactoryModel(fixture, **kwargs)

To be used in fixture definition (or in the kwargs of the fixture constructor) to reference a other fixture using the BaseFix.model() method.

Parameters:
  • fixture – Desired fixture
  • kwargsOptional: key words to overwrite properties of this fixture
Returns:

Proxy object for the desired fixture including the altered properties

class sqla_fix_fact.BaseFix(fix_fact, **kwargs)
Parameters:
  • fix_fact – instance of SqlaFixFact
  • kwargsOptional: key words to overwrite properties of this fixture
create()

Adds this model to the session. This instance is not registered and thus can never be referred to via get

Returns:SQLAlchemy Model instance
get()

returns an already existing model instance or creates one, registers it to be able to find it later and then returns the instance

Returns:SQLAlchemy Model instance
model()

Returns a model instance of this fixture which is ready to be added. The model itself is not added to the DB but all dependencies are.

Returns:SQLAlchemy Model instance

Indices and tables