Assuming the below sample code structure
# Returns a region
class RegionFactory:
.....
# Returns a user
class UserFactory():
.....
# Assigns a user with a permission against specific region
class RegionUserPermissionFactory():
user = UserFactory()
permission = PermissionFactory()
....
Now I would like to have a method that allows assigning "user region permission" and I think this method is much suited in the UserFactory because it resembles a real world-class template.
def add_user_domain_permission(permission_options, domain):
....
(Invokes RegionUserPermissionFactory to create objects relevant to
the user)
....
Edited 25/09/2019 Is it a better approach to include methods related to RegionUserPermission in the parent class? In my current codebase, there are a few more classes that include user object and is it better OOP practise to add them to UserFactory? Does it violate abstraction encapsulation concepts?
The idea of factory_boy is to define factories as relevant to your test cases; the set of attributes and parameters should be defined to provide a simple API when writing tests. The library focuses on providing readable and maintainable tests, rather than adhering strictly to OOP principles ;)
In your example, if some tests require a simple user and others want to attach it to a specific region, you could add a second factory dedicated to that use case, say a RegionalizedUserFactory
:
class RegionalizedUserFactory(UserFactory):
class Params:
# Tests would call RegionalizedUserFactory(region_code='xxx')
# Setting this as a `Param` means that the `region_code` field won't be passed
# to User.objects.create(...)
region_code = 'asia'
add_user_permission = factory.RelatedFactory(
# Once the `User` is created, call UserPermissionFactory(user=the_user, ...)
UserPermissionFactory, 'user',
# And use this factory's region_code attribute as the code for RegionFactory
permission__region__code=factory.SelfAttribute('....region_code'),
)
This would be used as follows:
>>> UserFactory() # No region perms
<User: John Doe, perms=[]>
>>> RegionalizedUserFactory() # Default on asia
<User: John Doe, perms=[<Permission: level=admin, region=<Region: asia>>]>
>>> RegionalizedUserFactory(region_code='mars') # Custom region
<User: John Doe, perms=[<Permission: level=admin, region=<Region: mars>>]>
You could also decide to merge both classes in a single declaration, using factory.Maybe
(which conditionally enables declarations based on other fields):
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = User
class Params:
region_code = None
add_user_permission = factory.Maybe(
'region_code',
factory.RelatedFactory(
UserRegionPermission, 'user',
permission__region__code=factory.SelfAttribute('....region'),
),
)
With that second factory:
>>> UserFactory() # No region_code => no perms
<User: John Doe, perms=[]>
>>> UserFactory(region_code='asia')
<User: John Doe, perms=[<Permission: level=admin, region=<Region: asia>>]>