Search code examples
javaunit-testingmockitotestngabstract-class

Testing concrete methods in abstract classes using subclasses


abstract class BaseClass{

      private final Dependency dep;

      BaseClass(final Dependency dep){
             this.dep = dep;
      }
      abstract void toBeImplementedBySubclasses();

      public int concreteMethod(){
              //Do some processing
              return any_integer;
      }
}
class DerivedOne{

      @Inject
      DerivedOne(final Dependency dep){
          super(dep);
      }       

      public void toBeImplementedBySubclasses(){
           //DO SOMETHING RELEVANT TO DERIVED ONE IMPLEMENTATION
      }
}
class DerivedTwo{

      @Inject
      DerivedOne(final Dependency dep){
          super(dep);
      }   

      public void toBeImplementedBySubclasses(){
           //DO SOMETHING RELEVANT TO DERIVED TWO IMPLEMENTATION
      }
}

I want to test concrete methods in abstract class. Is it ok if I test the concrete methods in base class in the unit test for any of the two derived classes, or is there any other way?

So, if write test for DerivedOne class, it will include test for all methods AND the concrete method of the base class as well. I know there is a thing "Mockito.CALLS_REAL_METHODS", using which we can test abstract classes, but in my case my base class has some dependencies which I initalise/inject using super() insider constructor of my derived classes, so I cant be doing it using CALLS_REALS_METHODS


Solution

  • There are two options which immediately come to mind here.

    Firstly, you could write an abstract test class, which handles testing these methods, and then the test classes for your concrete implementations do the rest. For example:

    public abstract class YourAbstractClassTest {
    
        protected abstract YourAbstractClass getInstance();
    
        @Test
        public void testThing() {
            final YourAbstractClass instance = this.getInstance();
            instance.callMethod();
    
            Assertions.assertTrue(instance.someProperties());
        }
    }
    

    Alongside:

    public class ConcreteSubclassTest extends YourAbstractClassTest {
        private final ConcreteSubclass instance = new ConcreteSubclass();
    
        @Override
        protected YourAbstractClass getInstance() {
            return this.instance;
        }
    
        @Test
        public void moreTesting() {
            this.instance.implementationSpecificMethod();
        }
    }
    

    You could also create a dummy subclass in a test class for it:

    public class AbstractClassTest {
        private final AbstractClass instance = new AbstractClass() {
            @Override
            public void abstractMethod() {
                throw new UnsupportedOperationException();
            }
        }
    
        @Test
        public void testThing() {
            this.instance.concreteMethod();
            // Just make sure this doesn't ever go near the
            // methods you dummied up above...
        }
    }