I’ve primarily used the following techniques to stub/mock out dependent classes while unit testing:
- Using a Dynamic Mocking Framework like Mockito, EasyMock, JMock, RhinoMock, etc
- Create a special subclass of the dependent class (or Interface) in the test package and use that to stub out dependency
- One can choose to create an anonymous inner class if a full new class in a separate file cannot be justified as an act of sanity.
- (Sometimes you might even subclass the class under test to inject behavior and dependency).
- Have the test implement or extend the dependent class
Let’s see each technique in action with an example:
Problem: We are building a Coffee Vending Machine controlling software and trying to test drive the Controller piece.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| public class Controller {
private Panel panel;
public Controller(final Panel panel) {
this.panel = panel;
//some start up logic here.
panel.display("Please select a coffee type");
}
public void selectedCoffee(final CoffeeType type) {
String price = "0.35$"; // some logic to compute price
panel.display("Please insert " + price);
}
} |
public class Controller {
private Panel panel;
public Controller(final Panel panel) {
this.panel = panel;
//some start up logic here.
panel.display("Please select a coffee type");
}
public void selectedCoffee(final CoffeeType type) {
String price = "0.35$"; // some logic to compute price
panel.display("Please insert " + price);
}
}
Controller does whatever magic it wants to do and then displays some message on the panel.
1
2
3
| public interface Panel {
void display(String msg);
} |
public interface Panel {
void display(String msg);
}
1. One way to test the controller is by using a Dynamic Mocking framework like Mockito:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| public class TestUsingMockingFramework {
private Panel panel = mock(Panel.class);
@Test
public void displayCoffeeSelectionMessageOnPowerUp() {
new Controller(panel);
verify(panel).display("Please select a coffee type");
}
@Test
public void displayPriceOnSelectingCoffee() {
Controller controller = new Controller(panel);
controller.selectedCoffee(CoffeeType.BLACK);
verify(panel).display("Please insert 0.35$");
}
} |
public class TestUsingMockingFramework {
private Panel panel = mock(Panel.class);
@Test
public void displayCoffeeSelectionMessageOnPowerUp() {
new Controller(panel);
verify(panel).display("Please select a coffee type");
}
@Test
public void displayPriceOnSelectingCoffee() {
Controller controller = new Controller(panel);
controller.selectedCoffee(CoffeeType.BLACK);
verify(panel).display("Please insert 0.35$");
}
}
2. Another technique is to create a TestPanel class in the testing folder:
1
2
3
4
5
6
7
8
| class TestPanel implements Panel {
public String msg;
@Override
public void display(final String msg) {
this.msg = msg;
}
} |
class TestPanel implements Panel {
public String msg;
@Override
public void display(final String msg) {
this.msg = msg;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| public class TestUsingHandCodedStub {
private TestPanel panel = new TestPanel();
@Test
public void displayCoffeeSelectionMessageOnPowerUp() {
new Controller(panel);
assertEquals("Please select a coffee type", panel.msg);
}
@Test
public void displayPriceOnSelectingCoffee() {
Controller controller = new Controller(panel);
controller.selectedCoffee(CoffeeType.BLACK);
assertEquals("Please insert 0.35$", panel.msg);
}
} |
public class TestUsingHandCodedStub {
private TestPanel panel = new TestPanel();
@Test
public void displayCoffeeSelectionMessageOnPowerUp() {
new Controller(panel);
assertEquals("Please select a coffee type", panel.msg);
}
@Test
public void displayPriceOnSelectingCoffee() {
Controller controller = new Controller(panel);
controller.selectedCoffee(CoffeeType.BLACK);
assertEquals("Please insert 0.35$", panel.msg);
}
}
3. If you don’t want the overhead of creating an extra TestPanel class, you can create an anonymous inner class instead.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| public class TestUsingAnonymousInnerClass {
private String msg;
private Panel panel = new Panel() {
@Override
public void display(final String message) {
msg = message;
}
};
@Test
public void displayCoffeeSelectionMessageOnPowerUp() {
new Controller(panel);
assertEquals("Please select a coffee type", msg);
}
@Test
public void displayPriceOnSelectingCoffee() {
Controller controller = new Controller(panel);
controller.selectedCoffee(CoffeeType.BLACK);
assertEquals("Please insert 0.35$", msg);
}
} |
public class TestUsingAnonymousInnerClass {
private String msg;
private Panel panel = new Panel() {
@Override
public void display(final String message) {
msg = message;
}
};
@Test
public void displayCoffeeSelectionMessageOnPowerUp() {
new Controller(panel);
assertEquals("Please select a coffee type", msg);
}
@Test
public void displayPriceOnSelectingCoffee() {
Controller controller = new Controller(panel);
controller.selectedCoffee(CoffeeType.BLACK);
assertEquals("Please insert 0.35$", msg);
}
}
4. One other technique I find useful sometimes is to have my test implement or extend the dependency (class or interface). So the test acts as the real dependency.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| public class TestUsingTestClassAsPanel implements Panel {
private String msg;
@Override
public void display(final String message) {
msg = message;
}
@Test
public void displayCoffeeSelectionMessageOnPowerUp() {
new Controller(this);
assertEquals("Please select a coffee type", msg);
}
@Test
public void displayPriceOnSelectingCoffee() {
Controller controller = new Controller(this);
controller.selectedCoffee(CoffeeType.BLACK);
assertEquals("Please insert 0.35$", msg);
}
} |
public class TestUsingTestClassAsPanel implements Panel {
private String msg;
@Override
public void display(final String message) {
msg = message;
}
@Test
public void displayCoffeeSelectionMessageOnPowerUp() {
new Controller(this);
assertEquals("Please select a coffee type", msg);
}
@Test
public void displayPriceOnSelectingCoffee() {
Controller controller = new Controller(this);
controller.selectedCoffee(CoffeeType.BLACK);
assertEquals("Please insert 0.35$", msg);
}
}
I’ve seen very few people use the last technique. Personally I think it has a place and time.
When would I use this technique?
- Sometimes this technique can be very simple (not worth introducing Dynamic Mocking Framework yet nor worth the over-head of extra test helper classes)
- I find this technique particularly useful when I don’t want to expose some state on the dependent class.
- This technique takes you more towards interaction based testing rather than state based testing.
This entry was posted
on Tuesday, September 29th, 2009 at 3:44 AM and is filed under Programming, Testing.
You can follow any responses to this entry through the RSS 2.0 feed.
You can skip to the end and leave a response. Pinging is currently not allowed.