Modular JUnit Behavior Tests

Over the years I've developed a way of doing unit tests in java that's a bit different from folks I work with.  I've pieced it together from various books I've read on unit testing, and from from experience of writing a bunch of these.

I'll walk through an simple example with FizzBuzz to show what I'm talking about, because explaining won't do it justice.  Note:  This is necessarily not the optimal way of TDD for FizzBuzz.  I've used it just as a simple example for showing the pattern.

What I do is split the tests into the classic 3 step pattern of Arrange, Act, Assert, doing this with a given/when/then language.

I Arrange with a factory method

public FizzBuzzRunner givenAFizzBuzzCalculator() {  
    return new FizzBuzzRunner(new FizzBuzzCalculator());  
}  

With more complicated setups, I'll augment the factory method by having it take a builder pattern.  Then you can say things like

givenAFizzBuzzCalculator(withX(X).withY(Y).withZ(Z));  

I Act with a runner class

private class FizzBuzzRunner {  
    private final FuzzBuzzCalculator fizzBuzz;  
    public FizzBuzzRunner(FizzBuzzCalculator fizzBuzz) {  
             this.fizzBuzz = fizzBuzz;  
    }  

    public FizzBuzzVerifier whenFizzBuzzRunsFromTo(int from, int to)     {  
         return new FizzBuzzVerifier(from, to);  
    }  
}  

I Verify the result with a verifier class

private class FizzBuzzVerifier {  
    private final List<String> fizzBuzzResult;  
    public FizzBuzzVerifier(List<String> result) {  
          this.fizzBuzzResult = result;  
    }  

    public void thenVerifyExpectedTextAtIndex(String expectedText, List<Integer> expectedIndices) {  
          for(int i=0;i<expectedIndices.size();i++) {  
               assertThat(fizzBuzzResult.get(i), is(equalTo(expectedText));  
          }  
    }  

     public void thenVerifyIndexNumberTextAtIndex(List<Integer> expectedIndices) {  
          for(int i=0;i<expectedIndices.size();i++) {  
               assertThat(fizzBuzzResult.get(i), is(equalTo(String.valueOf(i));  
          }  
    }  

}  

I Daisy Chain the testcases

    @Test public void  
    shouldReturnFizzOnMultiplesOfThree() {  
        givenAFizzBuzzCalculator().  
              whenFizzBuzzRunsFromTo(0, 30).
              thenVerifyExpectedTextAtIndex("Fizz", Arrays.asList(3, 6, 9, 12, 18, 21, 24, 27));  
    }

    @Test public void  
        shouldReturnBuzzOnMultiplesOfFive() {  
        givenAFizzBuzzCalculator().  
            whenFizzBuzzRunsFromTo(0, 30).  
            thenVerifyExpectedTextAtIndex("Buzz", Arrays.asList(5, 10, 20, 25));  
    }  

    @Test public void  
    shouldReturnFizzBuzzOnMultiplesOfFifteen() {  
        givenAFizzBuzzCalculator().  
            whenFizzBuzzRunsFromTo(0, 30).  
            thenVerifyExpectedTextAtIndex("FizzBuzz", Arrays.asList(15, 30);  
    }  

    @Test public void  
    shouldReturnTheNumberOnNonMultiplesOfThreeOrFive() {  
        givenAFizzBuzzCalculator().  
            whenFizzBuzzRunsFromTo(0, 30).  
            thenVerifyIndexNumberAtIndex( Arrays.asList(0,1,2,4,6,7,8,11,13,14,16,17,19,22,23,26,28,29));  
    }

I really like the approach because it has a few benefits...

  • Readability - The testcases themselves work as a DSL.  You can understand what the test is doing very easily.  It also breaks the code that runs the test into several objects, with like methods grouped together.
  • Reuse - Since my test logic is built into objects, I can reuse these for multiple tests.  I also try to write the execution methods in a way where I can pass in the variations in data.
  • Speed - Because the test lend themselves to more "DRY" programming, especially in more complicated scenarios, I'm writing less code.  Also, the objects follow a specific order, and each method returns the next object in the list.  This is awesome for command completion.  Imagine after typing your given method, you get a command complete menu of your "when" methods available.