Random Post: Are .NET Properties a Mistake?
RSS .92| RSS 2.0| ATOM 0.3
  • Home
  • About
  • Eric’s Toolset
  • Free Software
  • Software License
  •  

    Mock Object Strategies

    December 9th, 2003 by Eric

    How do you get your production code to use real objects and your test code to use mock objects?

    Here’s the class we want to test (adapted from Object-Oriented Design Heuristics, Arthur J. Riel):

    public class HeatFlowRegulator 
    { 
        private Furnace furnace; 
        private Set house;    
     
        public HeatFlowRegulator(Set house) 
        { 
            this.house = house; 
            furnace = new FurnaceImpl(); 
        }    
     
        public void poll() 
        { 
            Iterator iter = house.iterator(); 
            while (iter.hasNext()) 
            { 
                Room room = (Room)iter.next(); 
                if (room.isHeatNeeded()) 
                { 
                    furnace.on(); 
                    return; 
                } 
            }    
     
            furnace.off(); 
        } 
    }

    Here’s the test that we’d like to write:

    public void testFurnaceOnIfRoomNeedsHeat() 
    { 
        Set house = new HashSet(); 
        house.add(new Room(“Room needing heat”));    
     
        // Hmm… How do I make sure the room needs heat?    
     
        HeatFlowRegulator regulator = new HeatFlowRegulator(house); 
        regulator.poll();    
     
        // Hmm… How do I know the heat is on or not? 
    }

    It looks like we need two mock objects to stand in for Room and Furnace. We’ll assume here that Room and Furnace are/can be made interfaces. When that’s not true, things are a little more complicated (but still possible).

    Here’s our test after incorporating a mock Room:

    public void testFurnaceOnIfRoomNeedsHeat() 
    { 
        Set house = new HashSet(); 
        MockRoom room = new MockRoom(“Room needing heat”); 
        room.alwaysNeedHeat(); 
        house.add(room);    
     
        HeatFlowRegulator regulator = new HeatFlowRegulator(house); 
        regulator.poll();    
     
        // Hmm… How do I know the heat is on or not? 
    }

    Let’s suppose also that we create a mock Furnace that is an isOn method beyond the normal Furnace interface. But we’re still unable to finish the test because of this line in the class under test:

     furnace = new FurnaceImpl();

    Maybe we could have a property that can be set within the test to inform the HeatFlowRegulator class that it is being tested. The constructor changes to look something like this:

    public HeatFlowRegulator(Set house) 
    { 
        this.house = house; 
        if (testing()) 
            furnace = new MockFurnaceImpl(); 
        else 
            furnace = new FurnaceImpl(); 
    }

    We’ve successfully substituted the furnace with our mock version, but we’re still no closer to being able to finish the test, because the test can’t get the MockFurnace to ask whether it is on or not. Still, just replacing the real object might be a good first step in the case where a real Furnace implementation is really expensive to work with. This approach isn’t ideal, though, because the behavior of the class being tested is actually different when in a test situation.

    The following sections describe a few approaches for getting our mock object used that also allow us to finish our test.

    Constructor

    If we add a new constructor to HeatFlowRegulator, we can swap in our mock furnace class:

    public HeatFlowRegulator(Set house, Furnace furnace) 
    { 
        this.house = house; 
        this.furnace = furnace; 
    }

    The unit test can then be completed:

    public void testFurnaceOnIfRoomNeedsHeat() 
    { 
        Set house = new HashSet(); 
        MockRoom room = new MockRoom(“Room needing heat”); 
        room.alwaysNeedHeat(); 
        house.add(room);    
     
        MockFurnace furnace = new MockFurnace(); 
        HeatFlowRegulator regulator = new HeatFlowRegulator(house, furnace); 
        regulator.poll();    
     
        assertTrue(furnace.isOn()); 
    }

    A nice point about this change to HeatFlowRegulator is that existing clients continue to work exactly as before. A side effect is that the Furnace interface, which was invisible in the public interface before, is now present. If it really doesn’t make sense to expose it, we could give the new constructor package-level access, and be sure that the test class is in the same package.

    Setter

    If HeatFlowRegulator were to have lots of subclasses, it might be inconvenient to create a new constructor for each subclass. Instead we might choose to add a setter. The test code would change a little (see bolded line):

    public void testFurnaceOnIfRoomNeedsHeat() 
    { 
        Set house = new HashSet(); 
        MockRoom room = new MockRoom(“Room needing heat”); 
        room.alwaysNeedHeat(); 
        house.add(room);    
     
        MockFurnace furnace = new MockFurnace(); 
        HeatFlowRegulator regulator = new HeatFlowRegulator(house); 
        regulator.setFurnace(furnace); 
        regulator.poll();    
     
        assertTrue(furnace.isOn()); 
    }

    Of course, if the Furnace object needed to be used during construction, this approach wouldn’t work so well.

    Factory

    What if HeatFlowRegulator worked with multiple furnaces, and it created them as needed? This changes the example a fair amount, but it’s worthwhile to consider. What we want now is a factory for creating furnaces, and we want a mock factory to complement the real implementation. Setting the factory can be done with either of the previous techniques (constructor or setter). With our mock factory present, we can return any number of MockFurnace implementations. Here’s the new test method:

    public void testAnyFurnaceOnIfRoomNeedsHeat() 
    { 
        Set house = new HashSet(); 
        MockRoom room = new MockRoom(“Room needing heat”); 
        room.alwaysNeedHeat(); 
        house.add(room);    
     
        MockFurnaceFactory furnaceFactory = new MockFurnaceFactory(); 
        HeatFlowRegulator regulator = new HeatFlowRegulator(house, furnaceFactory); 
        regulator.poll();    
     
        assertTrue(furnaceFactory.isAnyOn()); 
    }

    We count on our mock factory to hang on to a reference to all the objects it creates so that we can implement the isAnyOn method.

    Static Factory

    If you want to hide the factory entirely from the non-test users of your class — that is, not change the public interface at all — you can also use this approach that David Jackman came up with. The basic idea is to have a static factory that can be replaced at test time with a mock factory instance.

    The implementation code calls the factory in a typical singleton fashion:

    Furnace furnace = FurnaceFactory.createFurnace();

    The factory itself looks like this:

    public class FurnaceFactory 
    { 
        protected static FurnaceFactory factoryImpl = new FurnaceFactory();    
     
        public static Furnace createFurnace() 
        { 
            return factoryImpl.createNewFurnace(); 
        }    
     
        // singleton, so no access to the constructor
        protected FurnaceFactory() 
        { 
        }    
     
        protected Furnace createNewFurnace() 
        { 
            return new FurnaceImpl(); 
        } 
    }

    Now you can create a derived mock factory factory to use in testing that exposes an extra method to override the factory that actually gets used:

    public class MockFurnaceFactory extends FurnaceFactory 
    { 
        public static void override() 
        { 
            factoryImpl = new MockFurnaceFactory(); 
        }    
     
        protected Furnace createNewFurnace() 
        { 
            return new MockFurnaceImpl(); 
        } 
    }

    You would need to call MockFurnaceFactory.override() before running your tests, perhaps in your setUp method. There is some danger here, though, because other tests in the system might need the real factory, and since it is stored in a static member you need to somehow get it back to the right instance.

    When to use a mock object

    From http://c2.com/cgi/wiki?MockObject

    • Real object has non-deterministic behavior
    • Real object is difficult to set up
    • Real object has behavior that is hard to cause (such as a network error)
    • Real object is slow
    • Real object has (or is) a UI
    • Test needs to query the object, but the queries are not available in the real object (for example, “was this callback called?”)
    • Real object does not yet exist

    Always remember, however, that tests that are decoupled from the class under test will be more robust than those that have intimate knowledge of the implementation. Tests are supposed to make it easier to change the implementation (without fear of breaking everything), not harder.

    Resources and References

    Leave a Reply