Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to call a private constructor of an abstract class for mocking.

I'm running into some issues with how I can test this code correctly. I want to be able to mock out the call to delegateForFoo either using a Mocking framework, or even though reflection. However, when I try to do this through reflection, or through PowerMock, I get the errors as shown in the code below.

@Test
public void testDelegatedMethodWithPowerMock() throws Exception {

    DelegateForFoo mockedDelegateForFoo = PowerMock
            .createMock(DelegateForFoo.class);

    Foo foo = Whitebox.invokeConstructor(Foo.class, mockedDelegateForFoo);
    // org.powermock.reflect.exceptions.ConstructorNotFoundException: Failed
    // to find a constructor with parameter types: at line above.

    Constructor<Foo> fooConstructor = Foo.class
            .getDeclaredConstructor(DelegateForFoo.class);
    fooConstructor.setAccessible(true);
    Foo foo2 = fooConstructor.newInstance(mockedDelegateForFoo);
    // java.lang.InstantiationException at line above.

}

I did my best to check previous stackOverflow questions and I didn't see one that addresses this issue directly. There are some answers on how to call private constructors of concrete classes, and some on how to class private methods on abstract classes, but none on this particular instance.

My official question is as follows: Is there a way to call a private constructor of an abstract class and pass in a mocked object for testing purposes.

I know generally you shouldn't be testing private methods, etc. However in this case, I want to test a constructor that has a legitimate reason to be private. There is no reason for Bar to know anything about the delegate at all and for proper encapsulation, it should be private. Thanks in advance.

The code that I am using is below.

public abstract class Foo {

    private DelegateForFoo delegateForFoo;
    private int valueToBeSetFromDelegate;

    Foo() {
        // I don't want the subclasses to do anything with this DelegateForFoo.
        // It's set through the constructor because there is a requirement that
        // Foo must have a delegateForFoo in order to be created.
        this(new DelegateForFoo());
    }

    // This Constructor is indirectly called by the subclasses. They don't know
    // anything about how it is set, or what it does.
    private Foo(DelegateForFoo delegateForFoo) {
        this.delegateForFoo = delegateForFoo;
        this.valueToBeSetFromDelegate = this.delegateForFoo.delegatedMethod();
    }

    int useTheDelegateForSomeMethod() {
        return this.delegateForFoo.delegatedMethod();
    }
}


public class DelegateForFoo {
    int delegatedMethod() {
        return 5;
    }
}



public class Bar extends Foo {
    // here, Bar doesn't care about the delegate for Foo at all. It only cares
    // that the method it is called has some implementation.
    int useDelegateForFooWithoutKnowingAboutIt() {
        return super.useTheDelegateForSomeMethod() + 10;
    }
}
like image 986
diminishedprime Avatar asked Mar 18 '23 23:03

diminishedprime


2 Answers

The reason why you can not create the instance of your Foo class is because it is abstract. There is no mechanism in Java that could create instance of an abstract class.

To test abstract class you need to define new class that will extend Foo. This mean that the default constructor will be invoked.

like image 196
Damian Leszczyński - Vash Avatar answered Apr 14 '23 14:04

Damian Leszczyński - Vash


As other people have pointed out, you're not going to be able to do anything with the Foo constructors class directly because of the abstract-ness of the class.

Having said that, you should be able to write a test for the Bar class if you think about it in a slightly different manner. Instead of mocking the Foo constructor calls, mock the DelegateForFoo constructor call.

I think I'd do it like this:

  • Use PowerMock to prepare the Foo, Bar and DelegateForFoo classes.
  • Set up a PowerMock Expectation for the DelegateForFoo constructor call that returns a Mock object for it.
  • Add the expectation for the delegatedMethod.
  • Create a Bar object and then call the useDelegateForFooWithoutKnowingAboutIt method
  • Don't forget to verify.

So it would look something like this: (I apologise for any typos, I've not tried this in an IDE)

@RunWith( PowerMockRunner.class )
@PrepareForTest( Foo.class, Bar.class, DelegateForFoo.class )
public class BarTest

    @Test
    public void thatDelegateForFooCanBeMocked() {
        DelegateForFoo mockDelegate = EasyMock.createMock(DelegateForFoo.class);
        EasyMock.expect( mockDelegate.delegatedMethod() ).andReturn( 5 );
        EasyMock.replay( mockDelegate );

        PowerMock.expectNew( DelegateForFoo.class ).andReturn( mockDelegate );
        PowerMock.replayAll();

        Bar bar = new Bar();
        int result = bar. useDelegateForFooWithoutKnowingAboutIt();

        Assert.assertThat( result, CoreMatchers.is(15) );

        PowerMock.verifyAll();
        EasyMock.verify(mockDelegate);
    }
}

Of course, documentation is a wonderful thing, so see the PowerMock: Mocking a Constructor documentation to find out more about constructor mocking with PowerMock.

like image 33
Dan Temple Avatar answered Apr 14 '23 15:04

Dan Temple