Mocking superclass method call using mockito is not working

Viewed 1785

I have been trying to stub the superclass method call from the subclass overridden method, but till now I am stuck without any luck to succeed. I have extensively searched on the google and SO questions as well.

Here is the test code that I am using. The problem is that, in my case, both the superclass and subclass methods are getting stubbed.

@Test(enabled = true)
public void superclassMockTest() throws Exception {

    ChildClass adc = getChildClass ();

    doReturn(getReturnObject())
            .when((SuperClass) adc).getObject(any(String.class), any(String.class), any(Map.class))
    
    ResultObject result= adc.getObject("acc", "abc", null);
    assertNotNull(result);
    assertNotNull(result.getPropertyValue("attribute"));
}

The property is set on the ResultObject in the Subclass's getObject(...) method. I want to stub the super.getObject(...) call within the subclass's to return some arbitrary object which is provided by getReturnObject() method.

The problem that is occurring is that: even the call ResultObject result= adc.getObject("acc", "abc", null); is getting stubbed and the property is not getting set, which is causing the problem.

I even tried adding: doCallRealMethod().when(adc).getObject(any(String.class), any(String.class), any(Map.class)); just before the actual call on the instance, hoping that the actual method on the instance is called. But in this case, the super.getObject(...) is not getting stubbed and getting executed.

It's kind of either or situation into which I am stuck, I can either stub both or can't stub any. Please help!

4 Answers

This cast will not take the effect you are trying:

((SuperClass) adc).getObject("", "", null)

The getObject method that is called is the one of the adc instance, regardless of the cast. The cast is compile-time sugar only.

You'll need to change the design, like using different method names, or composition instead of inheritance. Other possibility is to override the superclass in test runtime, copying a modified version to test source with same package of the original:

Like this:

src/main/java
             \-------- com.xyz.pac
                  \--------- SuperClass.java
                  \--------- ChildClass.java
src/test/java
             \-------- com.xyz.pac
                  \--------- SuperClass.java
             \-------- test.com.xyz
                  \--------- MyTest.java

This only affect your tests. Test packages are not available at runtime.

Theer are a few options

  • Refactor to favor composition over inheritance (there are lots of articles on the benefits and would be the preferred strategy)
  • Rename the the methods so they are different in Parent and Child
  • Move the call to super into a private method on the Child class
  • Change Parent using byte manipulation

Some examples use PowerMock and ByteBuddy

Example - Refactor

public class A {
    public String getName() {
        return "BOB";
    }
}

public class B {

    private final A a;

    public B(A a) {
        this.a = a;
    }

    public String getName() {
        String name = a.getName();
        return name;
    }
}

class BTest {

    @Test
    void shouldMockA() {
        A a = mock(A.class);
        when(a.getName()).thenReturn("JANE");
        assertThat(a.getName(), is("JANE"));
    }

}

Example - PowerMock Rename

** This example is using JUnit please follow this link to setup for TestNG **

public class Parent {
    
    public String getName() {
        return "BOB";
    }
    
}

public class Child extends Parent {

    public String getNameChild() {
        String name = super.getName();
        return name;
    }
}

@RunWith(PowerMockRunner.class)
@PrepareForTest(Parent.class)
public class ChildTest {

    private final Child child = PowerMockito.spy(new Child());

    @Test
    public void shouldMockParentSuperCall() {
        PowerMockito.doReturn("JANE").when(child).getName();
        assertThat(child.getNameChild(), is("JANE"));
    }

}

Example - PowerMock Private Method

** This example is using JUnit please follow this link to setup for TestNG **

public class Child extends Parent {

    public String getName() {
        String name = callParentGetName();
        return name;
    }

    private String callParentGetName() {
        return super.getName();
    }
}

@RunWith(PowerMockRunner.class)
@PrepareForTest({Child.class, Parent.class})
public class ChildTest {

    private final Child child = PowerMockito.spy(new Child());;

    @Test
    public void shouldMockParentSuperCallName() throws Exception {
        PowerMockito.doReturn("JANE").when(child, "callParentGetName");
        assertThat(child.getName(), is("JANE"));
    }

}

Example - ByteBuddy

** This is not recommended (look into Java Agents, Instrumentation ByteBuddy, etc) **

public class Parent {

    public String getName() {
        return "BOB";
    }

}

public class Child extends Parent {

    public String getName() {
        String name = super.getName();
        return name;
    }

}

class ChildTest {

    @Test
    void shouldChangeParentMethod() {
        ByteBuddyAgent.install();
        new ByteBuddy()
                .redefine(Parent.class)
                .method(named("getName"))
                .intercept(FixedValue.value("JANE"))
                .make()
                .load(
                        Parent.class.getClassLoader(),
                        ClassReloadingStrategy.fromInstalledAgent());

        Child child = new Child();

        assertThat(child.getName(), is("JANE"));

        // Remove anything added e.g. class transformers
    }

}

You should be able to change out the code which attempts the cast for:

adc.super.getObject();

As I stated in my comment above, the super keyword in Java is, according to the docs, a reference variable which gives a reference to the object's parent. If the cast is indeed the problem, this change should fix it.

This is an interesting question. When you use inheritance, even though you mock the parent class in your testcase, during autowiring of the child it always refers to the actual parent class, not the one mocked. This is something to do with Mock framework. Seems it is missing the capability of allowing Parent classes to be mocked during child's instantiation

  1. if you use composition instead of inheritance you will achieve the results. But I doubt anyone would want to change a good design to be able to execute a test case :D
@Component("parent")
public class Parent{   
    public String getMsg(){
        return "Parent";
    }
}
@Component
@Lazy
public class Child {
    @Autowired
    Parent parent; 
    public String getMsg(){
        return "Child" + parent.getMsg();
    }
}

The test case - 
 @MockBean
    @Qualifier("parent")
    Parent base;

 @BeforeEach
    public void initMockedBean(){
 when(base.getMsg()).thenReturn("Dummy"); }

  1. if it's possible to just test the Parent instead of the child, you can just try that

You can try raising the issue on Mockito. May be they will add it to features

While answering this question, I found this thread that confirms my answer - Mockito How to mock only the call of a method of the superclass There's also one suggestion in there. You can try to see if that helps you.

Related