Dilini Rajapaksha
Dilini Rajapaksha

Reputation: 2718

Inject mocks to Abstract class using mockito

I am mocking an abstract class like below:

myAbstractClass = Mockito.mock(MyAbstractClass.class, Mockito.CALLS_REAL_METHODS);

the problem is that MyAbstractClass has some dependencies injected via EJB annotations and there are not setters. Is there a way to inject the dependencies?

@InjectMocks does not work with Abstract classes.

Upvotes: 10

Views: 19887

Answers (6)

André Pereira
André Pereira

Reputation: 21

Personally, what I like to do is to extend the abstract with an anonymous class declared in the @Before (or @BeforeEach in junit.jupiter). This way I can achieve the following:

  • Don't mock the class that I want to test (like you are doing with Mockito.mock(MyAbstractClass.class, Mockito.CALLS_REAL_METHODS)), since that is kind of an anti-pattern. You only want to Mock the dependencies of the class you are testing;
  • You can provide Mocks for the dependencies of the abstract class, and that are called by non-abstract methods;
  • You can test non-abstract methods that call abstract methods

Example:

class TestAbstractClass {
    @Mock
    private ServiceDependency dependency;

    private AbstractClass abstractClass;

    @BeforeEach
    void setUp() {
        abstractClass= new AbstractClass (dependency) { };
    }

    @Test
    void test(){
        Mockito.when(dependency.someMethod()).thenReturn(something);
        var result = abstractclass.someNonAbstractMethod();
        // assertions
    }

}

Upvotes: 0

Rajat Verma
Rajat Verma

Reputation: 2590

Junit 4 specific solution

Abstract class that need to be tested

@Slf4j
public abstract class AdhocNotificationEmail {

    @Autowired
    protected CustomerNotificationRepository customerNotificationRepository;

    protected abstract  Map<String, String> abstractMethod(AdhocNotificationDTO adhocNotificationDTO);

    public JSONObject concreteMethod(){
      // some stuff that needs to be tested and common to all subclasses
    }
}

Test Class:

@RunWith(SpringJUnit4ClassRunner.class)
public class AdhocNotificationEmailTest{

    @Mock
    protected CustomerNotificationRepository customerNotificationRepository;

    private AdhocNotificationEmail unit;

    @Before
    public void setUp() {
        unit = new AdhocNotificationEmail() {
                   @Override
                   protected Map<String, String> abstractMethod(AdhocNotificationDTO notificationDTO) {
                    return null;
                   }
               };
        unit.customerNotificationRepository = customerNotificationRepository;
    }

    @Test
    public void concreteMethod_greenPath() {
        final String templateName = "NOTIFICATION_TEMPLATE";
        final AdhocNotificationDTO adhocNotificationDTOStub = getAdhocNotificationDTOStub(templateName);
        final CustomerNotification customerNotificationStub = getCustomerNotificationStub(templateName);
        when(customerNotificationRepository.findByIdAndTemplateName(id, templateName)).thenReturn(customerNotificationStub);
        final JSONObject response = unit.concreteMethod(adhocNotificationDTOStub);
        assertNotNull(response);
    }

Upvotes: 0

Christian Vega
Christian Vega

Reputation: 73

I am using junit5 for this.

What I did is instantiate the abstract class with a new abstractClass() in @BeforeEach and call the methods by super if the method is not abstract(using this because I have protected methods), after this I use ReflectionUtils.setField() to set the mocks in the abstract class and test every method and works pretty well. I leave a simple example that works.

AbstractClass

public abstract class AbstractClass {
  @Autowired
  private Environment environment;

  protected String getProperty(String property){
    return environment.getRequiredProperty(property);
  }
}

AbstractClassTest

@ExtendWith(MockitoExtension.class)
class AbstractClassTest {

  AbstractClass abstractClass;

  @Mock
  Environment environment;

  @BeforeEach
  void setUp() {
    abstractClass = new AbstractClass() {
      @Override
      public String getProperty(String property) {
        return super.getProperty(property);
      }
    };
    ReflectionTestUtils.setField(abstractClass, "environment", environment);
  }

  @Test
  void shouldReturnProperty() {
    String propertyValue = "this property";
    when(environment.getRequiredProperty("property")).thenReturn(propertyValue);
    String property = abstractClass.getProperty("property");
    assertEquals(propertyValue, property);
  }
}

This is just using mockito and junit5 to test. Remember to call ReflectionUtils after you instantiate the class with new AbstractClass() or the mocks won't be injected.

Any improve on this implementation is welcome :D.

Upvotes: 5

Sparwer
Sparwer

Reputation: 197

You can use the Powermock library to inject mocks in myAbstractClass using Whitebox.setInternalState(myAbstractClass, mock(MockedClass.class));

Upvotes: 1

Szymon Włodarczyk
Szymon Włodarczyk

Reputation: 127

Good practice is to write unit tests for all possible classes which inherits from this abstract class. Because in theory it is possible situation. This dependency should be mocked and you should forget about mocking dependencies of this EJB component. Maybe some code snippets would help to clarify what you try to achieve here.

Upvotes: -1

Craig
Craig

Reputation: 2376

Since you cannot instantiate an Abstract class there is nothing to test. I would recommend that you create child class (it could be a nested class inside your test class), and then run your tests that way. Then you can use the @Mock, @InjectMocks as you would normally.

Upvotes: 0

Related Questions