Chapter 3: Sensing and Separation

We break dependencies for two reasons:

  • Sensing — to get at values our code computes that we otherwise can’t observe from a test.
  • Separation — to get a piece of code into a test harness at all, when it won’t even run there.

Fake Objects

A fake object stands in for a real collaborator of the class under test. It impersonates the collaborator’s interface but does something simpler, so the test can run without the real dependency.

Here is an example

The two sides of a fake object

The confusing thing about fakes is that they have two “sides.” Take FakeDisplay, which implements a Display interface whose only method is showLine. That’s the side the production class (Sale) sees and uses. But the fake also exposes a second method, getLastLine, that exists purely for the test to read back what was displayed.

This is why the test holds the object as a FakeDisplay, not as a Display:

import junit.framework.*;
 
public class SaleTest extends TestCase {
    public void testDisplayAnItem() {
        FakeDisplay display = new FakeDisplay();
        Sale sale = new Sale(display);
        sale.scan("1");
        assertEquals("Milk $3.99", display.getLastLine());
    }
}

Sale only ever sees the Display interface, but the test needs the concrete FakeDisplay reference to call getLastLine() and sense what the sale displayed.

Mock Objects

Fakes are easy to write and great for sensing. If you find yourself writing a lot of them, a mock object is a more advanced kind of fake that performs the assertions internally — you tell it which calls to expect up front, then ask it to verify they happened.

import junit.framework.*;
public class SaleTest extends TestCase {
    public void testDisplayAnItem() {
        MockDisplay display = new MockDisplay();
        display.setExpectation("showLine", "Milk $3.99");
        Sale sale = new Sale(display);
        sale.scan("1");
        display.verify();
    }
}

Here the mock is told to expect showLine called with "Milk $3.99". You exercise the object as normal (scan()), then call verify(), which fails the test if any expectation wasn’t met. Mock frameworks are powerful and widely available, but they don’t exist in every language — and plain fakes are enough in most situations.

Source

Working Effectively with Legacy Code, Michael Feathers — Chapter 3, Sensing and Separation.

1 item under this folder.