XNSIO
  About   Slides   Home  

 
Managed Chaos
Naresh Jain's Random Thoughts on Software Development and Adventure Sports
     
`
 
RSS Feed
Recent Thoughts
Tags
Recent Comments

Different Techniques to Stub/Mock Dependencies

Tuesday, September 29th, 2009

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);
    }
}

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);
}

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$");
    }
}

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;
    }
}
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);
    }
}

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);
    }
}

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);
    }
}

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.

Refactoring Teaser IV Solution

Sunday, September 27th, 2009

Its been a while since the Fourth Refactoring Teaser was posted. So far, I think this is one of the trickiest refactorings I’ve tried. Refactored half of the solution and rewrote the rest of it.

Particularly thrilled about shrinkage in the code base. Getting rid of all those convoluted Strategies and Child Strategies with 2 main classes was real fun (and difficult as well).  Even though the solution is not up to the mark, its come a long long way from where it was.

Ended up renaming IdentityGenerator to EmailSuggester. Renamed the PartialAcceptanceTest to EmailSuggesterTest. Also really like how that test looks now:

28
29
30
private final User naresh_from_mumbai = new User("naresh", "jains", "mumbai", "india", "indian");
private final Context lets = new Context(userService, dns);
private final EmailSuggester suggester = new EmailSuggester(userService, dns, randomNumberGenerator);
32
33
34
35
36
@Test
public void suggestIdsUsingNameLocationAndNationality() {
    List<String> suggestions = suggester.optionsFor(naresh_from_mumbai);
    lets.assertThat(suggestions).are("[email protected]", "[email protected]", "[email protected]", "[email protected]");
}
38
39
40
41
42
43
@Test
public void avoidRestrictedWordsInIds() {
    lets.assume("naresh").isARestrictedUserName();
    List<String> suggestions = suggester.optionsFor(naresh_from_mumbai);
    lets.assertThat(suggestions).are("[email protected]", "[email protected]", "[email protected]", "[email protected]");
}
45
46
47
48
49
50
@Test
public void avoidCelebrityNamesInGeneratedIds() {
    lets.assume("naresh", "jains").isACelebrityName();
    List<String> suggestions = suggester.optionsFor(naresh_from_mumbai);
    lets.assertThat(suggestions).are("[email protected]", "[email protected]", "[email protected]", "[email protected]");
}
52
53
54
55
56
57
@Test
public void appendCurrentYearWithFirstNameIfIdIsNotAvailable() {
    lets.assume().identity("[email protected]").isNotAvailable();
    List<String> suggestions = suggester.optionsFor(naresh_from_mumbai);
    lets.assertThat(suggestions).are("[email protected]", "[email protected]", "[email protected]", "[email protected]");
}

EmailSuggester’s optionsFor() method turned out to be fairly straightforward.

26
27
28
29
30
31
32
33
34
public List<String> optionsFor(final User user) {
    List<String> ids = new ArrayList<String>();
    List<String> variations = asList(user.lastName, user.countryName, user.countryMoniker, user.city);
    for (String variation : variations) {
        UserData data = new UserData(user.firstName, variation, user.lastName);
        data.addGeneratedIdTo(ids);
    }
    return ids;
}

This method uses UserData class’ addGeneratedIdTo() method to add an email id to the list of ids passed in.

47
48
49
50
51
52
53
54
55
private void addGeneratedIdTo(final List<String> ids) {
    for (EmailData potential : buildAllPotentialEmailCombinations()) {
        String email = Email.create(potential.userName, potential.domain, dns);
        if (userService.isEmailAvailable(email)) {
            ids.add(email);
            break;
        }
    }
}

This method fetches all potential email address combination based on user data as follows:

57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
private List<EmailData> getAllPotentialEmailCombinations() {
    return new ArrayList<EmailData>() {
        {
            add(new EmailData(firstName, seed));
 
            if (seed != lastName) {
                add(new EmailData((firstName + lastName), seed));
                add(new EmailData((firstName + lastName.charAt(0)), seed));
            }
 
            add(new EmailData((firstName + currentYear()), seed));
 
            if (seed != lastName)
                add(new EmailData((firstName + lastName.charAt(0) + currentYear()), seed));
 
            for (int i = 0; i < MAX_RETRIES_FOR_RANDOM_NUMBER; ++i)
                add(new EmailData((firstName + randomNumber.next()), seed));
        }
    };
}

I’m not happy with this method. This is the roughest part of this code. All the

if (seed != lastName) {

seems dodgy. But at least all of it is in one place instead of being scattered around 10 different classes with tons of duplicate code.

For each potential email data, we try to create an email address, if its available, we add it, else we move to the next potential email data, till we exhaust the list.

Given two tokens (user name and domain name), the Email class tries to creates an email address without Restricted Words and Celebrity Names in it.

30
31
32
33
34
35
private String buildIdWithoutRestrictedWordsAndCelebrityNames() {
    Email current = this;
    if (isCelebrityName())
        current = trimLastCharacter();
    return buildIdWithoutRestrictedWordsAndCelebrityNames(current, 1);
}
37
38
39
40
41
42
43
44
45
46
private String buildIdWithoutRestrictedWordsAndCelebrityNames(final Email last, final int count) {
    if (count == MAX_ATTEMPTS)
        throw new IllegalStateException("Exceeded the Max number of tries");
    String userName = findClosestNonRestrictiveWord(last.userName, RestrictedUserNames, 0);
    String domainName = findClosestNonRestrictiveWord(last.domainName, RestrictedDomainNames, 0);
    Email id = new Email(userName, domainName, dns);
    if (!id.isCelebrityName())
        return id.asString();
    return buildIdWithoutRestrictedWordsAndCelebrityNames(id.trimLastCharacter(), count + 1);
}

Influenced by Functional Programming, I’ve tried to use Tail recursion and Immutable objects here.

Also to get rid of massive duplication in code, I had to introduce a new Interface and 2 anonymous inner classes.

5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public interface RestrictedWords {
    RestrictedWords RestrictedUserNames = new RestrictedWords() {
        @Override
        public boolean contains(final String word, final DomainNameService dns) {
            return dns.isRestrictedUserName(word);
        }
    };
 
    RestrictedWords RestrictedDomainNames = new RestrictedWords() {
        @Override
        public boolean contains(final String word, final DomainNameService dns) {
            return dns.isRestrictedDomainName(word);
        }
    };
 
    boolean contains(final String word, DomainNameService dns);
}

This should give you a decent idea of what the code does and how it does what it does. To check in detail, download the complete project source code.

Also I would recommend you check out some the comparison of code before and after.

    Licensed under
Creative Commons License