0

I'm having hard time to test a class(TestClass) which uses builder pattern(BuilderClass) in logic . I'm unable to mock builder class(BuilderClass). The following is simplified version of my logic.

public class TestClass {
    public int methodA() {
        ExternalDependency e = BuilerClass.builder().withName("xyz").withNumber(10).build();
        return e.callExternalFunction();
    }
}

And here is my builder class

public class BuilderClass {
     public static BuilderClass builder() { return new BuilderClass(); }
     int number;
     String name;
     public BuilderClass withName(String name) {
         this.name = name;
         return this;
     }
     public BuilderClass withNumber(int number) {
          this.number = number;
           return this;
     }
     public ExternalDependency build() {
           return new ExternalDependency(name,number);
     }
 }

For my test class, I'm using Mockito with Dataprovider.

 @RunWith(DataProviderRunner.class)
 class TestClassTest {
 @Mock private ExternalDependency e;
 @Mock private BuilderClass b;
 @InjectMocks private TestClass t;
 @Before public void setUp() { MockitoAnnotations.initMocks(this); }
 @Test public void testMethodA() {
    when(b.withName(any(String.class)).thenReturn(b); //This is not mocking
    when(b.withNumber(10)).thenReturn(b); //This is not mocking
    Assert.notNull(this.t.methodA()); //Control while execution is going to implementation of withName and withNumber, which should not happen right.
    }

Help me if I miss anything. Thanks }

hasanac
  • 168
  • 3
  • 13
  • 2
    This doesn't do what you expect because you're mocking the calls on the mocked `BuilderClass b` instance but `methodA` actually always receives a fresh, non-mocked instance of `BuilderClass` from the `BuilerClass.builder()` call. `@InjectMocks` has no effect because `TestClass` has no injectable dependencies. One obvious suggestion would be to move instantiation of `ExternalDependency` outside `methodA` to allow injecting the mocked instance when testing. – kryger Dec 16 '15 at 23:22

1 Answers1

1

Similar to what kryger said in a comment above, you probably need to do a refactor like this:

In your class under test, create a seam to replace e by a mock:

public class TestClass {
    public int methodA() {
        ExternalDependency e = buildExternalDependency("xyz", 10);
        return e.callExternalFunction();
    }

    protected ExternalDependency buildExternalDependency(String name, int number) {
          return BuilerClass.builder().withName(name).withNumber(number).build();       
    }

}

In the test code, override the test class to replace e with a mock and to validate the inputs to the builder:

   @RunWith(DataProviderRunner.class)
     class TestClassTest {
     @Mock private ExternalDependency e;
     private TestClass t;
     @Before public void setUp() { 
         MockitoAnnotations.initMocks(this); 
         t = new TestClass() {
             @Override
             protected ExternalDependency buildExternalDependency(String name, int number) {
                // validate inputs:
                Assert.assertEquals(10, number);
                Assert.assertEquals("xyz", name);
                return e;  // provide the mock      
             }    
        }
     }

     @Test public void testMethodA() {
        // TODO: mock behavior of callExternalFunction() here

            Assert.notNull(this.t.methodA()); 
    }
}

You may want to go further with the refactor to move buildExternalDependency() into a another class which could be mocked and injected in the constructor of TestClass.

aro_tech
  • 1,103
  • 7
  • 20