Factory Pattern for Testing Roles

PIN

I wanted to dig into the Factory pattern a bit. I was fighting the thought that conceptually, it’s just a class with methods, but that’s not entirely right. I think what I’m settling into is thinking of it as an object that has purpose. It’s not modeling something. It’s creating. It’s using a model, and churning different objects out.

In the Naja project, I have a Roles table in my database. I have a simple CRUD written out, and I have tests for them. I want to be able to generate role data painlessly and get it into the database for testing. I’m using the Faker library in Python to generate generic data. So my first step is to build the Role factory.

def build(**kwargs):
        defaults = {
            'role_discord_id': str(fake.random_int(
                                   min=10000000000000000, 
                                   max=99999999999999999)
                               ),  # 17-18 digits
            'role_name': fake.word()[:30],  # Ensure max 30 chars (under 32 limit)
            'role_description': fake.text(max_nb_chars=200)  # Keep under 255 limit
        }
        defaults.update(kwargs)
        return defaults

This creates the role with faked but default attributes. The attributes can be overwritten. For example, here’s one of the tests where two roles are created:

def test_getAllRoles(db_session):
    # Store some roles
    role_1 = RoleFactory.create(db_session, role_name="RoleOne")
    role_2 = RoleFactory.create(db_session, role_name="RoleTwo")

    # Retrieve all roles
    roles = getAllRoles()

    assert len(roles) >= 2  # At least the two created should be present
    assert any(role.role_name == "RoleOne" for role in roles)
    assert any(role.role_name == "RoleTwo" for role in roles)

The name has been specified by passing in role_name, and the factory takes care of the rest of the data. You may notice that it’s calling .create() instead of .build().

def create(db_session, **kwargs):
        role_data = RoleFactory.build(**kwargs)
        role = Role(**role_data)
        db_session.add(role)
        db_session.commit()
        return role

The RoleFactory builds the data and it’s stored in role_data which is a dictionary. Then it passes that information into role which is the actual role object. It then adds it to our database. Here’s the whole class:

class RoleFactory:
    
    #####################
    # Create a role data dictionary without saving to db
    #####################
    @staticmethod
    def build(**kwargs):
        defaults = {
            'role_discord_id': str(fake.random_int(
                                   min=10000000000000000,
                                   max=99999999999999999)
                               ),  # 17-18 digits
            'role_name': fake.word()[:30],  # Ensure max 30 chars (under 32 limit)
            'role_description': fake.text(max_nb_chars=200)  # Keep under 255 limit
        }
        defaults.update(kwargs)
        return defaults
    
    @staticmethod
    def create(db_session, **kwargs):
        role_data = RoleFactory.build(**kwargs)
        role = Role(**role_data)
        db_session.add(role)
        db_session.commit()
        return role