Search code examples
javaspringjunitmockito

Inject Mocks for objects created by Factory classes


I have the following class:

public class MyClass {        
    private Apple apple;

    public void myMethod() {
       apple = AppleFactory.createInstance(someStringVariable);
       ....
       ....
       ....
    }
}

And the Test class:

@RunWith(MockitoJUnitRunner.class)
public class MyClassTest {

        @InjectMocks 
        MyClass myClass;

        @Test
        public void myMethod(){
         ...
         ...
         ...
        }
    }

How could I inject an Apple instance as a mock in MyClass?


Solution

  • You have 3 possibilities to solve this:

    Abstract factory: Instead of using a static method, use a concrete factory class:

    public abstract class AppleFactory {
        public Apple createInstance(final String str);
    }
    
    public class AppleFactoryImpl implements AppleFactory {
        public Apple createInstance(final String str) { // Implementation }
    }
    

    In your test class, mock the factory:

    @RunWith(MockitoJUnitRunner.class)
    public class MyClassTest {
    
        @Mock
        private AppleFactory appleFactoryMock;
    
        @Mock
        private Apple appleMock;
    
        @InjectMocks 
        MyClass myClass;
    
        @Before
        public void setup() {
            when(appleFactoryMock.createInstance(Matchers.anyString()).thenReturn(appleMock);
        }
    
        @Test
        public void myMethod(){
         ...
         ...
         ...
        }
    }
    

    PowerMock: Use PowerMock to create a mock of a static method. Look at my answer to a relevant question to see how it's done.

    Testable class: Make the Apple creation wrapped in a protected method and create a test class that overrides it:

    public class MyClass {
       private Apple apple;
    
       public void myMethod() {
           apple = createApple();
           ....
           ....
           ....
       }
    
       protected Apple createApple() {
           return AppleFactory.createInstance(someStringVariable);
       }
    }
    
    
    @RunWith(MockitoJUnitRunner.class)
    public class MyClassTest {
    
        @Mock
        private Apple appleMock;
    
        @InjectMocks 
        MyClass myClass;
    
        @Test
        public void myMethod(){
         ...
         ...
         ...
        }
    
        private class TestableMyClass extends MyClass {
           @Override
           public void createApple() {
              return appleMock;
           }
        }
    }
    

    Of course, in your test class you should test TestableMyClass and not MyClass.

    I'll tell you my opinion on each of the methods:

    1. The abstract factory method is the best one - This is a clear design that hides the implementation details

    2. The testable class - Is the second option which requires minimum changes

    3. The PowerMock option is my least favorite - Instead of going for a better design, you ignore and hide your problem. But that's still a valid option.