Search code examples
pythonpython-3.xtestingclass-methodfactory-boy

Factory calling alternate constructor (classmethod)


I am struggling finding a way to have a class Factory (I use factory_boy version 2.11.1 with Python 3) using an alternate constructor defined as a @classmethod.

So let's say we have a class for building a 2D-point object with a default constructor and 2 additional ones:

class Point:

    def __init__(self, x, y):
        self.x = x
        self.y = y

    @classmethod
    def fromlist(cls, coords):  # alternate constructor from list
        return cls(coords[0], coords[1])

    @classmethod
    def duplicate(cls, obj):  # alternate constructor from another Point
        return cls(obj.x, obj.y)

I create a basic Point factory:

import factory

class PointFactory(factory.Factory):
    class Meta:
        model = Point
        inline_args = ('x', 'y')

    x = 1.
    y = 2.

By default, it seems to call the constructor __init__ of the class which seems very logical. I could not find a way to pass inline_args as being coords for using the alternate constructor fromlist. Is there a way to do so?

This is my first experience working and building factories in general so I may also be looking up at the wrong keywords on the web...


Solution

  • The point of factory_boy is to make it easy to produce test instances. You'd just call PointFactory() and you are done, you have test instances for the rest of your code. This usecase doesn't need to use any of the alternative constructors, ever. The factory would just use the main constructor.

    If you are thinking that factory_boy factories must be defined to test your extra constructors, then you have misunderstood their use. Use factory_boy factories to create test data for other code to be tested. You'd not use them to test the Point class (other than to generate test data to pass to one of your constructors).

    Note that inline_args is only needed if your constructor doesn't accept keyword arguments at all. Your Point() class has no such restriction; x and y can be used both as positional and as keyword arguments. You can safely drop inline_args from your definition, the factory will work regardless.

    If you must use one of the other constructors (because you can't create test data with the main constructor), just pass the specific constructor method in as the model:

    class PointListFactory(factory.Factory):
        class Meta:
            model = Point.fromlist
    
        coords = (1., 2.)