Q:

How to unit test factory in python?

When unit testing class one should test only against public interface of its collaborators. In most cases, this is easily achieved replacing collaborators with fake objects - Mocks. When using dependency injection properly, this should be easy most times.
However, things get complicated when trying to test factory class. Let us see example
Module wheel
class Wheel:
    """Cars wheel"""

     def __init__(self, radius):
         """Create wheel with given radius"""

         self._radius = radius #This is private property

Module engine
 class Engine:
     """Cars engine"""

     def __init(self, power):
     """Create engine with power in kWh"""

         self._power = power #This is private property

Module car
class Car:
    """Car with four wheels and one engine"""

    def __init__(self, engine, wheels):
        """Create car with given engine and list of wheels"""

        self._engine = engine
        self._wheels = wheels

Now let us have CarFactory
from wheel import Wheel
from engine import Engine
from car import Car

class CarFactory:
    """Factory that creates wheels, engine and put them into car"""

    def create_car():
        """Creates new car"""

        wheels = [Wheel(50), Wheel(50), Wheel(60), Wheel(60)]
        engine = Engine(500)
        return Car(engine, wheels)

Now I want to write an unit test for the CarFactory. I want to test, the factory creates objects correctly. However, I should not test the private properties of objects, because they can be changed in future and that would break my tests. Imagine, Wheel._radius replaced by Wheel._diameter or Engine._power replaced by Engine._horsepower.
So how to test the factory?

A:

Fortunately in python testing factories is easy thanks to monkey_patching. You can replace not only instances of objects, but also whole classes. Let us see example
import unittest
import carfactory
from mock import Mock

def constructorMock(name):
    """Create fake constructor that returns Mock object when invoked"""
    instance = Mock()
    instance._name_of_parent_class = name
    constructor = Mock(return_value=instance)
    return constructor

class CarFactoryTest(unittest.TestCase):

    def setUp():
        """Replace classes Wheel, Engine and Car with mock objects"""

        carfactory.Wheel = constructorMock("Wheel")
        carfactory.Engine = constructorMock("Engine")
        carfactory.Car = constructorMock("Car")

    def test_factory_creates_car():
        """Create car and check it has correct properties"""

        factory = carfactory.CarFactory()
        car_created = factory.create_car()

        # Check the wheels are created with correct radii
        carfactory.Wheel.assert_called_with(radius=50)
        carfactory.Wheel.assert_called_with(radius=50)
        carfactory.Wheel.assert_called_with(radius=60)
        carfactory.Wheel.assert_called_with(radius=60)

        # Check the engine is created with correct power
        carfactory.Engine.assert_called_once_with(power=500)

        # Check the car is created with correct engine and wheels
        wheel = carfactory.Wheel.return_value
        engine = carfactory.Engine.return_value
        carfactory.Car.assert_called_once_with(engine, [wheel, wheel, wheel, wheel])

        # Check the returned value is the car created
        self.assertEqual(car_created._name_of_parent_class, "Car")

So we replace the classes and their constructors with Mock, that returns our fake instance. That enable us to check, the constructor was called with correct parametres and so we do not need to rely on real classes. We are really able in python to use not only fake instances, but also fake classes.
Also, I have to mention, the code above is not ideal one. For example, fake constructor should really create new Mock for each request, so we can check the car is called with correct wheels (correct order for example). This could be done,  but the code would be longer and I wanted to keep the example as simple as possible.
In the example I have used Mock library for python http://www.voidspace.org.uk/python/mock/
But it is not necessary.

