0

I am getting null pointer exception while I am trying to mock

@WebMvcTest(IMnJobManager.class)
public class CMnJobManagerTest {

    @Autowired
    private MockMvc mockmvc;

    @Test
    public void testExample()throws Exception{
        IMnAllWorkFlows allWorkFlows = Mockito.mock(IMnAllWorkFlows.class);

        Mockito.doAnswer(
                invocation -> {
                    return Arrays.asList( "modn-ops");
                }).when(allWorkFlows).getAllTenants();

        mockmvc.perform(get("/v1/tenant"))
                .andExpect(status().isOk())
                .andExpect(content().string("modn-ops"))
                .andDo(print());
    }
}

I am getting following error:

java.lang.NullPointerException
        at com.test.manager.CMnJobManagerTest.testExample(CMnJobManagerTest.java:32)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:566)
        at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
        at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
        at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
        at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
        at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
        at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
        at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
        at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
        at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
        at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
        at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
        at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
        at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
        at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
        at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:252)
        at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:141)
        at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:112)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:566)
        at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray(ReflectionUtils.java:189)
        at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:165)
        at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:85)
        at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:115)
        at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:75)

My interface implementation class looks like below:

@RestController
public class CMnJobManager implements IMnJobManager {
@Autowired
    public CMnJobManager(IMnAllWorkFlows allWorkFlows, IMnWorkflowService workflowService,
        IMnTemporalServiceClient temporalServiceClient, IMnWorkflowHistoryService workflowHistoryService,
        IMnSearchAttributeService searchAttributeService, IMnS3Tenant s3Tenant) {
    this.allWorkFlows = allWorkFlows;
    this.workflowService = workflowService;
    this.searchAttributeService = searchAttributeService;
    this.workflowHistoryService = workflowHistoryService;
    this.temporalServiceClient = temporalServiceClient;
    this.s3Tenant = s3Tenant;
    }
.
.
.

@Autowired
    private HttpServletRequest request;

    @Autowired
    private CMnCustomMetricService customMetricService;

}

The interface has many methods but I am trying to mock only one. The rest call, will make a call to the IMnJobManager interface and hence I have mocked it.

Error says the issue is at mockMvc.perform(...) and hence the NullPointerException.

When I remove Autowired from MockMvc, and add mockMvc = MockMvcBuilders.standaloneSetup(allWorkFlows).build();, it gives 404.

Any idea what might be wrong here?

Swaraj Shekhar
  • 187
  • 1
  • 7
  • 28
  • Are you saying that the problem you want help with is no longer the problem you describe in your question? Then edit your question. – tgdavies May 19 '23 at 23:41
  • Your variable `allWorkFlows` is never used, how do you expect it to be used by your production code? – knittl May 22 '23 at 18:31
  • Did you try annotating with `@AutoConfigureMockMvc`? – knittl May 22 '23 at 18:32
  • allWorkflows gets used in the API call. – Swaraj Shekhar May 22 '23 at 19:19
  • After adding @WebMvcTest(IMnJobManager.class) @RunWith(SpringRunner.class) @AutoConfigureMockMvc, it gives unsatisfied dependnecies – Swaraj Shekhar May 22 '23 at 19:19
  • Then you must satisfy your dependencies. What do you mean with "it gets used in the API call"? According to your code, it is not used _anywhere_. A _local_ variable is assigned, a call is stubbed; but then it is not used anymore – knittl May 22 '23 at 20:24
  • @SwarajShekhar did the provided answer solve your problem? It shows which annotations to use, how to scan for your controllers, how to define and wire mocked dependencies, and how to stub the method invocations. – knittl May 30 '23 at 05:47

1 Answers1

0

There seems to be a lot of confusion what the annotations do and how to correctly use Mockito to set up mock objects/test doubles.

To get started, I recommend to read the following posts:

Let's look at some of the mistakes first and in the end come back to write a working test.

@WebMvcTest(IMnJobManager.class)

The value property of the @WebMvcTest annotation specifies the Controllers to load in the Spring context. IMnJobManager is not a controller, but your job manager interface. You want to pass CMnJobManager.class instead.

@WebMvcTest only loads the web context of your application (such as the controllers). From the JavaDoc of the annotation:

Using this annotation will disable full auto-configuration and instead apply only configuration relevant to MVC tests (i.e. @Controller, @ControllerAdvice, @JsonComponent, Converter/GenericConverter, Filter, WebMvcConfigurer and HandlerMethodArgumentResolver beans but not @Component, @Service or @Repository beans).

So none of your Service beans will be loaded.

Further down in the same JavaDoc:

Typically @WebMvcTest is used in combination with @MockBean or @Import to create any collaborators required by your @Controller beans.

What does that mean?

  1. @WebMvcTest(IMnJobManager.class) will not create any endpoints, because IMnJobManager is not a controller and does not define any request mappings. This line explicitly tells Spring to not load your real controller. Trying to request any endpoint (with mockmvc.perform(get(…)) will result in a 404/NOT_FOUND.
  2. No services are loaded and we must (manually) create the collaborators for your controller. We'll handle this in the next section.
@Test
public void testExample()throws Exception{
    IMnAllWorkFlows allWorkFlows = Mockito.mock(IMnAllWorkFlows.class);

    doAnswer(invocation -> Arrays.asList("modn-ops"))
            .when(allWorkFlows)
            .getAllTenants();

    mockmvc.perform(get("/v1/tenant"))
            ...
}

This code creates a new Mockito mock object, stubs a method on it, and then … does nothing with it. The instance is local to the method and does not exist outside of it, nor is it accessible from the outside. It is not registered in the application context and it cannot be used by your controller. (Now's a good time to read the linked Stackoverflow post a second time).

How do we make the instance available as collaborator to our controller? We do what the JavaDoc of the annotation tells us: define it as @MockBean:

@MockBean
private IMnAllWorkFlows allWorkFlows;

And then stub the method in the setup/@BeforeEach method. In simple tests such as in the question, the stubbing could be configured in the test itself too.

@BeforeEach
void setUp() {
    when(allWorkFlows.getAllTenants()).thenReturn("modn-ops");
    // or: doAnswer(a -> Arrays.asList("modn-ops")).when(allWorkFlows).getAllTenants();
}

Fixing all of the above should:

  1. Load the web context
  2. Instantiate the controller
  3. Create collaborators
  4. Set up stubbings for the methods

Here's a simple, yet full, demo project (i.e. a Minimal, Reproducible Example with all classes required to compile and execute the code) which shows that the solution outlined above does indeed work:

DemoService.java:

public interface DemoService {
    String get();
}

@Service
class DemoServiceImpl implements DemoService {
    @Override
    public String get() { return Instant.now().toString(); }
}

DemoController.java:

@RestController
@RequiredArgsConstructor
public class DemoController {
    private final DemoService demoService;

    @GetMapping("demo")
    public String demo() { return demoService.get(); }
}

WebApplicationTest.java:

@WebMvcTest(DemoController.class) // only loads a single controller
class WebApplicationTest {
    @Autowired private MockMvc mockMvc;
    @MockBean  private DemoService demoService; // add mock bean to context

    @BeforeEach
    void setUp() {
        // stub method:
        when(demoService.get()).thenReturn("stubbed value");
    }

    @Test
    void test() throws Exception {
        // perform GET request:
        mockMvc.perform(get("/demo"))
                .andExpect(status().isOk())
                .andExpect(content().string("stubbed value"))
                .andDo(print());
    }
}
knittl
  • 246,190
  • 53
  • 318
  • 364