简体   繁体   中英

junit: NullPointer on injected bean when testing controller with mockmvc

So I have this test case:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:rest-context-test.xml")
public class CustomerControllerTest {

private MockMvc mockMvc;

@Before
public void setup() {
    this.mockMvc = MockMvcBuilders
            .standaloneSetup(new CustomerController()).build();
}


@Test
public void testGetCustomerById() throws Exception {
    mockMvc.perform(get("/customers/{ccid}", 1)).andExpect(status().isOk())
            .andExpect(content().contentType("application/json"))
            .andExpect(jsonPath("$.ccid").value("1"));
}

}

That loads rest-context-test.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
    http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd">

<bean id="customerServiceImpl"
    class="myorg.service.impl.MockCustomerServiceImpl" />

</beans>

With the hope of being able to AutoWire customerServiceImpl into my controller:

@Controller
@RequestMapping("customers")
public class CustomerController {

private static Logger Logger = LoggerFactory
        .getLogger(CustomerController.class);

@Autowired
@Qualifier("customerServiceImpl")
private CustomerService customerService;


@RequestMapping(value = "/{ccid}", method = RequestMethod.GET, produces = "application/json")
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public Customer getCustomerById(@PathVariable Integer ccid)
        throws BadRequestException, ServerErrorException,
        ResourceNotFoundException {
    Customer c = null;

    try {
        c = customerService.getCustomer(ccid);
    } catch (IllegalArgumentException iae) {
        // HTTP 400
        Logger.error("Invalid arguments submitted", iae);
        throw new BadRequestException();
    } catch (CsspException ce) {
        // HTTP 500
        Logger.error("Server error", ce);
        throw new ServerErrorException();
    }

    if (c == null) {
        // HTTP 404
        throw new ResourceNotFoundException();
    }

    return c;
}
}

But my unit test is throwing the following trace:

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.NullPointerException
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:965)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:844)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:689)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:829)
at org.springframework.test.web.servlet.TestDispatcherServlet.service(TestDispatcherServlet.java:66)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:802)
at org.springframework.mock.web.MockFilterChain$ServletFilterProxy.doFilter(MockFilterChain.java:168)
at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:136)
at org.springframework.test.web.servlet.MockMvc.perform(MockMvc.java:141)
at myorg.rest.controller.CustomerControllerTest.testGetCustomerById(CustomerControllerTest.java:41)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:83)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:231)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:88)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:174)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:46)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: java.lang.NullPointerException
at myorg.rest.controller.CustomerController.getCustomerById(CustomerController.java:134)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:215)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:132)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle    (ServletInvocableHandlerMethod.java:104)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod    (RequestMappingHandlerAdapter.java:745)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal    (RequestMappingHandlerAdapter.java:686)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:80)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:925)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:856)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:953)
... 38 more

This null pointer is thrown when the Controller tries to access customerService:

@Autowired
@Qualifier("customerServiceImpl")
private CustomerService customerService;

Why isn't my MockCustomerServiceImpl being AutoWired into the Controller?

Note: package names have been changed to protect the innocent

From Spring documentation :

The "webAppContextSetup" loads the actual Spring MVC configuration resulting in a more complete integration test. Since the TestContext framework caches the loaded Spring configuration, it helps to keep tests running fast even as more tests get added. Furthermore, you can inject mock services into controllers through Spring configuration, in order to remain focused on testing the web layer.

The "standaloneSetup" on the other hand is a little closer to a unit test. It tests one controller at a time, the controller can be injected with mock dependencies manually, and it doesn't involve loading Spring configuration. Such tests are more focused in style and make it easier to see which controller is being tested, whether any specific Spring MVC configuration is required to work, and so on. The "standaloneSetup" is also a very convenient way to write ad-hoc tests to verify some behavior or to debug an issue.

From this I understand that, if I need an .xml as a Spring configuration file to be loaded, then I need to go with the "webAppContextSetup". If I just need very simple tests with my Controller, without the need of a Spring configuration, then I go with the "standaloneSetup" approach. In your case, I think you need the "webAppContextSetup".

You forgot to create your mock objects and then inject them into your controller like so

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:rest-context-test.xml")
public class CustomerControllerTest 
{
    @Mock
    private CustomerService customerService;
    @InjectMocks
    private CustomerController customerController;

    private MockMvc mockMvc;

    @Before
    public void setup() 
    {
        MockitoAnnotations.initMocks(this);
        this.mockMvc = MockMvcBuilders
                .standaloneSetup(customerController).build();
    }


    @Test
    public void testGetCustomerById() throws Exception 
    {
        mockMvc.perform(get("/customers/{ccid}", 1)).andExpect(status().isOk())
                .andExpect(content().contentType("application/json"))
                .andExpect(jsonPath("$.ccid").value("1"));
    }
}

It helped me to fix the NullPointerException by adding annotations @RunWith(SpringRunner.class) , @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) and @AutoConfigureMockMvc

Here is code that worked for me-

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class MyTestClass {
    @Autowired
    private MockMvc mockMvc;

    @Test
    public void myClassController() throws Exception {

        Object obj = new Object(); 

        obj.setA(1);
        obj.setB(2);
        obj.setC(3);

        ObjectMapper mapper = new ObjectMapper();

        String jsonString = mapper.writeValueAsString(obj); // this is the json body
 
        mockMvc.perform(post("/ENDPOINT")
                .contentType(MediaType.APPLICATION_JSON)
                .content(jsonString))
                .andDo(print())
                .andExpect(status().isOk());
              
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM