4

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!

theimpatientcoder
  • 1,184
  • 3
  • 19
  • 32
  • 1
    It is hard to really understand your question unless you provide the code you are testing – Jocke Dec 11 '20 at 20:18
  • Well, its exactly as I have explained. Consider a typical scenario. There is a certain super class, a child class and then I want to stub the XYZ() method from the Super class and just want to execute the same XYZ() method from the Child class as is. The actual logic is in the SuperClass's method which returns a ResultObject, the ChildClass's method just sets few more things on the ResultObject! I want to stub the super.XYZ() call from ChildClass that would return some arbitrarily built ResultObject. That is what doReturn(..).when((SuperClass)...) is supposed to do – theimpatientcoder Dec 12 '20 at 14:24
  • The problem is: doReturn(getReturnObject()) .when((SuperClass) adc).getObject(any(String.class), any(String.class), any(Map.class)); This line is even stubbing the call to the ChildClass's XYZ() method. So, the call to ChildClass's XYZ() method is returning the arbitrary ResultObject, which in fact should have happened when it is calling super.XYZ() call within itself. Due to this, the extra logic within the ChildClass's XYZ() method is not getting executed and the extra properties are not getting set on the ResultObject! and the Test case fails on that validation. – theimpatientcoder Dec 12 '20 at 14:30
  • For more information on the code, both the methods (overriding and overridden) methods are public and nothing more weird or peculiar about it. Straight forward scenario. Curious on understanding why this might be happening? What could cause the ChildClass's method get stubbed with the Test code that I have posted in the Question. – theimpatientcoder Dec 12 '20 at 14:33
  • https://stackoverflow.com/questions/3467801/mockito-how-to-mock-only-the-call-of-a-method-of-the-superclass solution given in this SO answers is not working for me. I would also like if anybody can give me some pointers on how(to be more precise, what) should I debug in order to understand what is happening in my case? – theimpatientcoder Dec 12 '20 at 14:50
  • 4
    so add the code to your question, why should someone need to read your comments to the question in order to understand it? – Jocke Dec 14 '20 at 08:11
  • Also, are you missing a set of parentheses surrounding the entire 'when' block? The way it stands, you are returning via the doReturn(getReturnObject()) before the getObject() method is called. In other words, both child and super methods are outside of the stub method. The way I originally read it, it would return after the getObject() method was called. – Nate T Dec 21 '20 at 06:10

4 Answers4

3

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.

Gilliard Macedo
  • 386
  • 1
  • 6
  • can you provide an example for : Other possibility is to override the superclass in test runtime, copying a modified version to test source with same package of th original. – theimpatientcoder Dec 18 '20 at 13:16
  • What if he were to call adc.Super.getObject(...) ; ? Would that call parent method? Super ref variable returns a ref to parent. – Nate T Dec 21 '20 at 03:17
  • @NathanToulbert We are still talking about the test? Yes, super.getObject() will call SuperClass.getObject from src/test/java package. Edit: Just saw your answer. The keyword 'super' (lowercase) is only available inside the class code. So, it can't be done inside test class. – Gilliard Macedo Dec 21 '20 at 14:40
  • @Gilliard Macedo I believe you are thinking about the method `super()`. This refers to the parent constructor and can only be called from the first line of the child constructor. The `super` keyword, which was included in my answer, returns a ref to the parent object and can be called from anywhere in the code(within reason, obviously XD ). The capital is a typo. I actually but I cannot remember if I can do single character edits on my own posts or not. I thought I tried Sunday and was denied, but I could be wrong. – Nate T Dec 24 '20 at 06:20
  • I will try editing once more, to be sure. If it is still showing as uppercase for you, you will know that it is beyond me to change (without a work-around). Here is a [reference](https://www.geeksforgeeks.org/difference-between-super-and-super-in-java-with-examples/#:~:text=super()-,The%20super%20keyword%20in%20Java%20is%20a%20reference%20variable%20that,to%20refer%20parent%20class%20constructors.&text=super()%20can%20be%20used%20to%20call%20parent%20class'%20constructors%20only.) for the reply above. Tried to keep it to a single comment, but the url is huge, even after shortening. – Nate T Dec 24 '20 at 06:20
  • @NathanToulbert Did you tried doing this? It can't be possible because of ambiguity. "super" inside class A is the parent object instance of A. instanceOfB.super is a compilation error. – Gilliard Macedo Dec 24 '20 at 21:33
1

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
    }

}
clD
  • 2,523
  • 2
  • 22
  • 38
  • Composition is preferred in certain circumstances, as is inheritance. It depends on the relationship between objects (has-a or is-a). These two functionalities (extending and class nesting) were added to oop languages like Java specifically to represent these relationships. As time goes on, it seems like we're finding every reason possible to get away from this convention. – Nate T Dec 21 '20 at 04:05
1

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.

Nate T
  • 727
  • 5
  • 21
0

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.