0

I have an abstract class which has a dependency which is being autowired:

public abstract class ClassB {
    @Autowired
    private ClassC classC;

    public String getValue() {
        classC.getSomeMethod();
    }
}

I've a class which extends this abstract class:

@Component
public class ClassA extends ClassB {
    @Autowired
    private ClassD classD;

    public String getClassAMethod() {
        String value = getValue();
        String dReturn = classD.getD();
        return value + dReturn;
    }
}

Now while doing UnitTesting I can do:

public class ClassATest {
    @Mock
    private ClassC classC;

    @Mock
    private ClassD classD;

    @InjectMocks
    private ClassA classA;

    @Test
    public void testSomething() {
        when(classC.getSometMethod()).thenReturn("classC");
        when(classD.getD()).thenReturn("classD");
        assertEquals(classA.getClassAMethod(), "classCclassD");
    }

}

This works fine, however if I use constructor injection for ClassA I get a Null pointer exception for classC.

@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class ClassA extends ClassB {
    private final ClassD classD;

    public String getClassAMethod() {
       String value = getValue();
       String dReturn = classD.getD();
       return value + dReturn;
    }
}

In the second case I even tried replacing InjectMocks with a normal constructor invocation, but ClassC object doesn't get mocked.

Stack trace: at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.mockito.internal.runners.DefaultInternalRunner$1.run(Unknown Source) at org.mockito.internal.runners.DefaultInternalRunner.run(Unknown Source) at org.mockito.internal.runners.StrictRunner.run(Unknown Source) at org.mockito.junit.MockitoJUnitRunner.run(Unknown Source) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68) at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47) at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

user1692342
  • 5,007
  • 11
  • 69
  • 128
  • How about showing us the code that fails, rather then the code that doesn't? – JB Nizet Jul 20 '18 at 06:30
  • @JBNizet Updated. It was the same method for the 2nd style as well hence didn't explicitly put it out there. But now I have updated to include it – user1692342 Jul 20 '18 at 06:31
  • I don't see any constructor anywhere. – JB Nizet Jul 20 '18 at 06:32
  • 1
    You didn't show a version of `ClassB` that has a constructor argument, and since it's neither in the constructor nor required, `RequiredArgs` doesn't cover it. (Also note that as of Spring 4.3, you don't need the annotation on a constructor if it's the only one.) – chrylis -cautiouslyoptimistic- Jul 20 '18 at 06:32
  • A NullPointerException always tells you exactly what is wrong, so please add the stack trace! – ewramner Jul 20 '18 at 06:33
  • @JBNizet RequiredArgsConstructor from Lombok takes care of it – user1692342 Jul 20 '18 at 06:34
  • @chrylis ClassB is an abstract class. ClassA which extends it has constructor. https://stackoverflow.com/questions/19965829/spring-can-you-autowire-inside-an-abstract-class – user1692342 Jul 20 '18 at 06:39
  • @user1692342 apparently it doesn't. I know nothing about Lombok, but that is where the problem is. Write that in plain Java, using constructor injection in both classes, and it will work fine. Then see how to translate that with Lombok if you really think it's worth it (it isn't, IMHO, given that you can't explain why your code doesn't work, which shows that the code is less readable than if it didn't use Lombok). – JB Nizet Jul 20 '18 at 06:45
  • @user1692342 You need to revisit basics about how inheritance and constructors work in Java. – chrylis -cautiouslyoptimistic- Jul 20 '18 at 08:44
  • @JBNizet The problem in this case isn't Lombok (and it's not making anything less clear); the problem is that the OP doesn't understand how constructors work in the first place and is leaving the superclass uninitialized (expecting the dependency to appear out of absolutely nowhere). – chrylis -cautiouslyoptimistic- Jul 20 '18 at 08:46
  • @chrylis The abstract class is initialized by Spring since there is an autowired field. As mentioned earlier: https://stackoverflow.com/questions/19965829/spring-can-you-autowire-inside-an-abstract-class – user1692342 Jul 20 '18 at 18:31

1 Answers1

2

Lombok's @AllArgsConstructor and @RequiredArgsConstructor only cover fields that are declared in the class itself. Fields from superclasses are not considered, as lombok cannot access those classes, because they are not yet resolved at the time lombok runs during the compilation process.

As a result in your case, the constructor that is generated for ClassA only has ClassD classD as argument, but not ClassC classC. (You can see that in the class outline view of your IDE, or by delomboking your code.) Therefore, classC remains uninitialized, causing the NPE.

So lombok cannot help you in your case, unfortunately. You have to write your constructor manually.

Jan Rieke
  • 7,027
  • 2
  • 20
  • 30