1

I am trying to mock a constructor method. But failed with messages said unnecessary stubbings. What modify should I make to succesfully run this unit test? Plus, Better not to modify the version of mockito.

Here are the example code:

Dependency:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <version>2.2.6.RELEASE</version> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-module-junit4</artifactId> <version>2.0.9</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-api-mockito2</artifactId> <version>2.0.9</version> <scope>test</scope> </dependency> 

Class A:

public class A { public void test() { B b = new B(); System.out.println(b.test()); } } 

Class B:

public class B { public String test() { throw new RuntimeException("Actual"); } } 

Unit Test:

import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import org.mockito.junit.jupiter.MockitoExtension; import org.powermock.core.classloader.annotations.PrepareForTest; import static org.powermock.api.mockito.PowerMockito.when; import static org.powermock.api.mockito.PowerMockito.whenNew; @ExtendWith(MockitoExtension.class) @RunWith(MockitoJUnitRunner.class) @PrepareForTest({A.class}) class ATest { @InjectMocks private A a; @Mock private B b; @Test void mainTest() { when(b.test()).thenReturn("mockedMath"); try { whenNew(B.class).withNoArguments().thenReturn(b); } catch (Exception e) { throw new RuntimeException(e); } a.test(); } } 

Change ATest to adapt answer

import org.junit.jupiter.api.Test; import org.powermock.core.classloader.annotations.PrepareForTest; import static org.powermock.api.mockito.PowerMockito.mock; import static org.powermock.api.mockito.PowerMockito.when; import static org.powermock.api.mockito.PowerMockito.whenNew; @PrepareForTest({A.class}) class ATest { @Test void mainTest() { try { B b = mock(B.class); when(b.test()).thenReturn("mockedMath"); whenNew(B.class).withNoArguments().thenReturn(b); A a = new A(); a.test(); } catch (Exception e) { throw new RuntimeException(e); } } } 

Got Exception

java.lang.RuntimeException: java.lang.RuntimeException: Actual at priv.django47.mocknew.ATest.mainTest(ATest.java:21) 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.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:675) at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60) at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:125) at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:132) at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:124) at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:74) at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115) at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105) at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:104) at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:62) at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:43) at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:35) at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104) at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:202) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:198) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:135) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:69) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:135) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125) at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122) at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80) at java.util.ArrayList.forEach(ArrayList.java:1259) at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125) at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122) at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80) at java.util.ArrayList.forEach(ArrayList.java:1259) at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125) at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122) at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80) at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32) at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57) at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51) at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:229) at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$6(DefaultLauncher.java:197) at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:211) at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:191) at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:128) at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:57) at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38) at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11) at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35) at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:231) at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55) Caused by: java.lang.RuntimeException: Actual at priv.django47.mocknew.B.test(B.java:5) at priv.django47.mocknew.A.test(A.java:6) at priv.django47.mocknew.ATest.mainTest(ATest.java:19) ... 65 more 

Change to Junit4, Still same Exception

import org.junit.Test; import org.powermock.core.classloader.annotations.PrepareForTest; import static org.powermock.api.mockito.PowerMockito.mock; import static org.powermock.api.mockito.PowerMockito.when; import static org.powermock.api.mockito.PowerMockito.whenNew; @PrepareForTest({A.class}) public class ATest { @Test public void mainTest() { try { B b = mock(B.class); when(b.test()).thenReturn("mockedMath"); whenNew(B.class).withNoArguments().thenReturn(b); A a = new A(); a.test(); } catch (Exception e) { throw new RuntimeException(e); } } } 

Final dependency that worked:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <exclusions> <exclusion> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-inline</artifactId> <version>${mockito.version}</version> <scope>test</scope> </dependency> 
10
  • Commercial support for Spring Boot 2.2 ended more than 3 years ago. If you have the possibility, you should upgrade your dependencies. (But you are not using Spring in your test, so you can remove it altogether) Commented Jul 3 at 14:18
  • Oh! I only noticed it now. Why are you preparing class A? You are overriding the constructor of class B, so you need to prepare this class. I will update my answer accordingly. But do note again that Powermock does not support JUnit 5. Have you tried plain Mockito? Commented Jul 3 at 18:35
  • This is just a demo from a large project. Upgrading spring boot and mockito all comes with a costs. Still same exception. So they are the least expectations. Can you successfully run the code on your local? Commented Jul 4 at 2:59
  • Yes, it works for me (using mockedConstruction from plain Mockito), the output is "mockedMath". Poweremock does not work with JUnit 5 Commented Jul 4 at 5:51
  • Can you show me your dependency structure? Better modified base on my dependency. Commented Jul 4 at 7:24

1 Answer 1

1
 @InjectMocks private A a; 

will create a new instance of A – before any of your tests have run.

whenNew(B.class).withNoArguments().thenReturn(b); 

only runs in your test – after A has been created due to @InjectMocks. You also need to prepare B to be mocked.

Solution: Drop @InjectMocks and create the instance manually after you have stubbed the constructor:

@PrepareForTest({B.class}) // You need to prepare B, not A class ATest { @Test void mainTest() { try { B b = mock(B.class); when(b.test()).thenReturn("mockedMath"); whenNew(B.class).withNoArguments().thenReturn(b); A a = new A(); a.test(); } catch (Exception e) { throw new RuntimeException(e); } } } 

PS. Powermock is not compatible with JUnit 5, so you need to run your test with JUnit 4. Remove the @ExtendWith, drop the org.junit.jupiter.* imports and only import from org.junit.*.

If you were to use JUnit 5 (the org.junit.jupiter.* imports), drop the @RunWith, which is JUnit 4. You should not have both at the same time.


Also note that this medium post from 2020 states that PowerMock is not compatible with JUnit 5.

Power mock is used to mock static methods, private methods. Power mock is not compatible with JUnit5 So we will discuss it will JUnit4.)

There's also an open GitHub issue to support JUnit 5: https://github.com/powermock/powermock/issues/830

You might want to use mockConstruction from plain Mockito instead:

When using the inline mock maker, it is possible to generate mocks on constructor invocations within the current thread and a user-defined scope. This way, Mockito assures that concurrently and sequentially running tests do not interfere. To make sure a constructor mocks remain temporary, it is recommended to define the scope within a try-with-resources construct.

To define mock behavior and to verify static method invocations, use the MockedConstruction that is returned.

Example below:

class ATest { @Test void mainTest() { try (MockedConstruction mocked = mockConstruction(B.class, (b, ctx) -> when(b.test()).thenReturn("mockedMath"))) { A a = new A(); a.test(); } } } 

See Baeldung for more examples.


Or, if you have the possibillity, change your code to not require mocking constructor calls at all:

public class A { private final Supplier<B> bSupplier; public A() { this(B::new); } A(final Supplier<B> bSupplier) { this.bSupplier = bSupplier; } public void test() { B b = bSupplier.get(); System.out.println(b.test()); } } 

And then in your test:

class ATest { @Test void mainTest() { A a = new A(() -> { B b = mock(B.class); when(b.test()).thenReturn("mockedMath") return b; }); // or: new A(() -> when(mock(B.class).test()).thenReturn("mockedMath").getMock()); a.test(); } } 
Sign up to request clarification or add additional context in comments.

8 Comments

Your solution returns a NullPointerException at the 1st line of method mainTest, I added the definitions of B "private final B b = mock(B.class)" then the exception disappeared, but there is no console output can be seen, why is that? Also, why "@Mock\n private B b" still returns NullPointerException? And springboot 2.2.6.RELEASE comes with mockito-core:3.1.0, I will need to study the impact when trying to update it to a MockedConstruction-supported version which is 3.5.10.
@Django47 whoops. I have corrected by examples. If you assign a value to the field, make sure you return the same reference from whenNew(…).thenReturn(…). @Mock-annotations only have an effect when you @ExtendWith your test with the appropriate extension (MockitoExtension)
Change "new A" to "mock(A.class)" then can run, but still no console output; After adding @ExtendWith(MockitoExtension.class), an "unnecessary stubbings "message showed.
@Django47 why would you want to have mock(A.class)? You are testing A; if you mock it, you no longer have a real A. Mockito mocks do nothing by default.
I had another look at your impl and I missed that B is only created inside test(), not inside A's constructor. Let me update my answer to handle this
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.