【问题标题】:Spring Controller Testing using Mockito , Null Pointer Exception使用 Mockito 进行 Spring 控制器测试,空指针异常
【发布时间】:2016-08-04 21:10:08
【问题描述】:

我正在尝试使用 Mockito 测试 Spring 控制器。我根据这个 question 模拟了对象也使用了 when() ,但我仍然面临空指针异常。请提出解决此异常的方法。

Github repository of this project

链接到的特定行空指针是

 modelMap.put("categories", simpleCategoryDAO.getAllCategories());    

我已经模拟了 simpleCategoryDAO 并使用when() 返回getAllCategories() 的列表。

测试方法:

@RunWith(MockitoJUnitRunner.class)
public class CategoryControllerTest {   

    private MockMvc mockMvc;

    @InjectMocks
    private CategoryController categoryController;

    @Mock
    private SimpleCategoryDAO simpleCategoryDAO;

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

    @Test
    public void categories_ShouldRenderCategoriesView() throws Exception {  
        List<Category> ALL_CATEGORIES = Arrays.asList(
                new Category(1,"Funny"),
                new Category(2,"JoyFul")
                );  
        Mockito.when(simpleCategoryDAO.getAllCategories()).thenReturn(ALL_CATEGORIES);  

        mockMvc.perform(get("/categories"))
        //.andExpect((MockMvcResultMatchers.model()).attribute("categories",ALL_CATEGORIES));
        .andExpect(MockMvcResultMatchers.view().name("categories"));
    }



}

控制器代码

@Controller
public class CategoryController {

    @Autowired
    SimpleCategoryDAO simpleCategoryDAO;

    @Autowired
    SimpleGifDAO simpleGifDAO;

    @RequestMapping("/categories")
    public String getAllCategories(ModelMap modelMap) {     
        modelMap.put("categories", simpleCategoryDAO.getAllCategories());       
        return "categories";        
    }

    @RequestMapping("/category/{categoryID}")
    public String getGifsByCategoryID(@PathVariable int categoryID,ModelMap modelMap){
        modelMap.put("gifs", simpleGifDAO.findGifsByCategoryID(categoryID));    
        modelMap.put("category",simpleCategoryDAO.getCategoryByID(categoryID));
        return "category";
    }
}

例外:

java.lang.NullPointerException: null
    at com.teja.controller.CategoryController.getAllCategories(CategoryController.java:23) ~[classes/:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_73]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_73]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_73]
    at java.lang.reflect.Method.invoke(Method.java:497) ~[na:1.8.0_73]
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:221) ~[spring-web-4.1.7.RELEASE.jar:4.1.7.RELEASE]
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:137) ~[spring-web-4.1.7.RELEASE.jar:4.1.7.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:110) ~[spring-webmvc-4.1.7.RELEASE.jar:4.1.7.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:776) ~[spring-webmvc-4.1.7.RELEASE.jar:4.1.7.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:705) ~[spring-webmvc-4.1.7.RELEASE.jar:4.1.7.RELEASE]
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) ~[spring-webmvc-4.1.7.RELEASE.jar:4.1.7.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:959) ~[spring-webmvc-4.1.7.RELEASE.jar:4.1.7.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893) ~[spring-webmvc-4.1.7.RELEASE.jar:4.1.7.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:967) [spring-webmvc-4.1.7.RELEASE.jar:4.1.7.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:858) [spring-webmvc-4.1.7.RELEASE.jar:4.1.7.RELEASE]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:622) [tomcat-embed-core-8.0.23.jar:8.0.23]
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:843) [spring-webmvc-4.1.7.RELEASE.jar:4.1.7.RELEASE]
    at org.springframework.test.web.servlet.TestDispatcherServlet.service(TestDispatcherServlet.java:65) [spring-test-4.1.7.RELEASE.jar:4.1.7.RELEASE]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:729) [tomcat-embed-core-8.0.23.jar:8.0.23]
    at org.springframework.mock.web.MockFilterChain$ServletFilterProxy.doFilter(MockFilterChain.java:167) [spring-test-4.1.7.RELEASE.jar:4.1.7.RELEASE]
    at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:134) [spring-test-4.1.7.RELEASE.jar:4.1.7.RELEASE]
    at org.springframework.test.web.servlet.MockMvc.perform(MockMvc.java:144) [spring-test-4.1.7.RELEASE.jar:4.1.7.RELEASE]
    at CategoryControllerTest.categories_ShouldRenderCategoriesView(CategoryControllerTest.java:46) [classes/:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_73]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_73]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_73]
    at java.lang.reflect.Method.invoke(Method.java:497) ~[na:1.8.0_73]
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) [junit-4.12.jar:4.12]
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) [junit-4.12.jar:4.12]
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) [junit-4.12.jar:4.12]
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) [junit-4.12.jar:4.12]
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26) [junit-4.12.jar:4.12]
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) [junit-4.12.jar:4.12]
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78) [junit-4.12.jar:4.12]
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57) [junit-4.12.jar:4.12]
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) [junit-4.12.jar:4.12]
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) [junit-4.12.jar:4.12]
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) [junit-4.12.jar:4.12]
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) [junit-4.12.jar:4.12]
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) [junit-4.12.jar:4.12]
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363) [junit-4.12.jar:4.12]
    at org.mockito.internal.runners.JUnit45AndHigherRunnerImpl.run(JUnit45AndHigherRunnerImpl.java:37) [mockito-core-1.10.19.jar:na]
    at org.mockito.runners.MockitoJUnitRunner.run(MockitoJUnitRunner.java:62) [mockito-core-1.10.19.jar:na]
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86) [.cp/:na]
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) [.cp/:na]
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459) [.cp/:na]
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675) [.cp/:na]
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382) [.cp/:na]
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192) [.cp/:na]

【问题讨论】:

    标签: java spring spring-mvc mocking mockito


    【解决方案1】:

    问题出在你的test class Before method you are instantiating new controller

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

    这是我如何测试 Controller

    控制器类:

    @Controller
    public class CategoryController {
    
        private SimpleCategoryDAO simpleCategoryDAO;
        private SimpleGifDAO simpleGifDAO;
    
        @Autowired
        public void setSimpleGifDAO(SimpleGifDAO simpleGifDAO) {
            this.simpleGifDAO = simpleGifDAO;
        }
    
        @Autowired
        public void setSimpleCategoryDAO(SimpleCategoryDAO simpleCategoryDAO) {
            this.simpleCategoryDAO = simpleCategoryDAO;
        }
    
        @RequestMapping("/categories")
        public String getAllCategories(ModelMap modelMap) {
            modelMap.put("categories", simpleCategoryDAO.getAllCategories());
            return "categories";
        }
    
        @RequestMapping("/category/{categoryID}")
        public String getGifsByCategoryID(@PathVariable int categoryID, ModelMap modelMap) {
            modelMap.put("gifs", simpleGifDAO.findGifsByCategoryID(categoryID));
            modelMap.put("category", simpleCategoryDAO.getCategoryByID(categoryID));
            return "category";
        }
    }
    

    请注意,我在这里使用的是 setter 注入,而不是字段注入。您也可以使用构造函数注入(我的首选方式)。

    在你的测试课中

    @RunWith(MockitoJUnitRunner.class)
    public class CategoryControllerTest {
    
        private MockMvc mockMvc;
    
        @Mock
        private SimpleCategoryDAO simpleCategoryDAO;
    
        @Before
        public void setup() {
            final CategoryController categoryController = new CategoryController();
    
            //notice here I'm setting the mocked dao here
            // if you didn't use @RunWith(MockitoJUnitRunner.class)
            // you can do: simpleCategoryDAO = Mockito.mock(SimpleCategoryDAO.class);
    
            categoryController.setSimpleCategoryDAO(simpleCategoryDAO);
    
            mockMvc = MockMvcBuilders.standaloneSetup(categoryController).build();
        }
    
        @Test
        public void categories_ShouldRenderCategoriesView() throws Exception {
            List<Category> ALL_CATEGORIES = Arrays.asList(
                    new Category(1, "Funny"),
                    new Category(2, "JoyFul")
            );
            Mockito.when(simpleCategoryDAO.getAllCategories()).thenReturn(ALL_CATEGORIES);
    
            mockMvc.perform(get("/categories"))
                    //.andExpect((MockMvcResultMatchers.model()).attribute("categories",ALL_CATEGORIES));
                    .andExpect(MockMvcResultMatchers.view().name("categories"));
        }
    }
    

    在测试中查看 Before method。我在我创建的 controllernew instance 上设置 mocked DAO,然后创建 MockMvc 使用控制器的相同实例。

    【讨论】:

    • 不使用mockMvc,直接调用它的方法来测试CategoryController不是更简单吗?并且可以通过自定义断言方法验证 @RequestMapping 等注释的存在。只是想知道......
    • 可以不用mockmvc对控制器做简单的测试。该方法将仅将控制器方法作为普通组件而不是 mockmvc 进行测试,这里我们正在模拟对处理程序的实际调用
    • 是的,但是“模拟对处理程序的实际调用”究竟能给我们带来什么好处?
    • 我们可以检查HttpStatus代码是否正确返回。我们可以检查指定为 GET 的方法是否可以通过 POST 访问,反之亦然。我们可以检查返回什么样的内容。如果我们将返回的内容设置为 JSON,我们可以检查我们的测试是否实际返回了 JSON。再次,我们可以使用 jsonPath 库来检查 json 是否完全按照我们想要发送的方式创建
    【解决方案2】:

    我对提供的解决方案有一些问题,所以我想分享一下我是如何解决的。我的问题是,当控制器在 DAO 上调用一个方法时,它会抛出 NullPointerException,因为我不想使用 setter 注入将模拟的 DAO 提供给控制器(我拒绝在 DAO 中创建一个 setter 方法只是为了能够测试一切)。

    基本上对于它需要的 OP:

    1. 不在 setup() 方法中实例化控制器(如 Aman 指出的那样)
    2. 在控制器声明之前使用@Mock 注释声明DAO。

    像这样:

    @RunWith(MockitoJUnitRunner.class)
    public class CategoryControllerTest {   
    
        private MockMvc mockMvc;
    
        @Mock
        private SimpleCategoryDAO simpleCategoryDAO;
    
        @InjectMocks
        private CategoryController categoryController;
    
        @Before
        public void setup() {
            mockMvc = MockMvcBuilders.standaloneSetup(categoryController).build();
        }
    
        @Test
        public void categories_ShouldRenderCategoriesView() throws Exception {  
            List<Category> ALL_CATEGORIES = Arrays.asList(
                    new Category(1,"Funny"),
                    new Category(2,"JoyFul")
                    );  
            Mockito.when(simpleCategoryDAO.getAllCategories()).thenReturn(ALL_CATEGORIES);  
    
            mockMvc.perform(get("/categories"))
            //.andExpect((MockMvcResultMatchers.model()).attribute("categories",ALL_CATEGORIES));
            .andExpect(MockMvcResultMatchers.view().name("categories"));
        }
    
    
    
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-11-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-27
      • 1970-01-01
      • 2021-06-15
      • 2015-10-07
      相关资源
      最近更新 更多