如何为Spring Boot Controller端点编写单元测试


69

我有一个带有以下内容的示例Spring Boot应用程序

引导主类

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

控制者

@RestController
@EnableAutoConfiguration
public class HelloWorld {
    @RequestMapping("/")
    String gethelloWorld() {
        return "Hello World!";
    }

}

为控制器编写单元测试的最简单方法是什么?我尝试了以下操作,但它抱怨无法自动连接WebApplicationContext

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = DemoApplication.class)
public class DemoApplicationTests {

    final String BASE_URL = "http://localhost:8080/";

    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

    @Before
    public void setup() {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
    }

    @Test
    public void testSayHelloWorld() throws Exception{

         this.mockMvc.perform(get("/")
                 .accept(MediaType.parseMediaType("application/json;charset=UTF-8")))
                 .andExpect(status().isOk())
                 .andExpect(content().contentType("application/json"));
    }

    @Test
    public void contextLoads() {
    }

}

3
注释尝试DemoApplication@WebAppConfiguration。如果那不起作用,您也可以添加它的代码吗?
奥古斯托(Augusto)

Answers:


47

Spring MVC提供了一个StandaloneSetup,它支持测试相对简单的控制器,而无需上下文。

通过注册一个或多个@Controller的实例并以编程方式配置Spring MVC基础结构来构建MockMvc。与普通的单元测试类似,这可以完全控制控制器的实例化和初始化以及它们的依赖关系,同时还可以一次测试一个控制器。

控制器的示例测试可以很简单

public class DemoApplicationTests {

    private MockMvc mockMvc;

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

    @Test
    public void testSayHelloWorld() throws Exception {
        this.mockMvc.perform(get("/")
           .accept(MediaType.parseMediaType("application/json;charset=UTF-8")))
           .andExpect(status().isOk())
           .andExpect(content().contentType("application/json"));

    }
}

这个测试是什么?我已经用when(employeeService.getById(1))。thenReturn(employee)进行了尝试;如果我证明退货,我如何确定它能按预期工作?
Dimitri Kopriwa

该示例仅讨论了基本期望,但是您可以使用Hamcrest匹配器进一步细化,并声明主体中的任何内容,例如根据您的期望实际json响应。基本上,无论你希望与Employee实例发生,你可以断言为好,无论是改造JSON或XML,性能等的重命名
主从

.andExpect(content()。contentType(“ application / json”)); 这行给我一个错误“该类型的方法content()未定义”。
杰西

4
对于上面的示例代码,您需要一些静态导入。因为content()那是org.springframework.test.web.servlet.result.MockMvcResultMatchers.content。顺便说一句:我建议使用MediaType常量,例如MediaType.APPLICATION_JSON和,MediaType.APPLICATION_JSON_UTF8而不要使用示例中基于字符串的值。
尼尔斯·布劳尼斯

31

Spring Boot中首次推出的新测试改进1.4.M2可以帮助减少编写此类情况所需的代码量。

测试看起来像这样:

import static org.springframework.test.web.servlet.request.MockMvcRequestB‌​uilders.get; 
import static org.springframework.test.web.servlet.result.MockMvcResultMat‌​chers.content; 
import static org.springframework.test.web.servlet.result.MockMvcResultMat‌​chers.status;

    @RunWith(SpringRunner.class)
    @WebMvcTest(HelloWorld.class)
    public class UserVehicleControllerTests {

        @Autowired
        private MockMvc mockMvc;

        @Test
        public void testSayHelloWorld() throws Exception {
            this.mockMvc.perform(get("/").accept(MediaType.parseMediaType("application/json;charset=UTF-8")))
                    .andExpect(status().isOk())
                    .andExpect(content().contentType("application/json"));

        }

    }

请参见博客帖子的更多细节,以及该文档


7
这是行不通的。您仍然会收到java.lang.IllegalStateException: Unable to find a @SpringBootConfiguration, you need to use @ContextConfiguration or @SpringBootTest(classes=...) with your test错误消息。
卢卡斯

1
spring-boot 1.4.0和一起使用时效果很好junit
user218867 '16

我也没有发现任何麻烦...如果能重现该错误,我将非常
高兴

1
@Lucas您得到的错误是由于spring-boot用于搜索上下文初始化程序的算法所致,您需要在测试类的包中或其子包中使用@SpringBootApplication或标记的应用程序类@SpringBootConfiguration,请参见:github。 com / spring-projects / spring-boot / issues / 5987
user218867 '16

3
添加静态导入会很不错:import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 导入静态org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; 导入静态org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
tibi

7

这是使用Spring MVC的standaloneSetup的另一个答案。使用这种方式,您可以自动连接控制器类或模拟它。

    import static org.mockito.Mockito.mock;
    import static org.springframework.test.web.server.request.MockMvcRequestBuilders.get;
    import static org.springframework.test.web.server.result.MockMvcResultMatchers.content;
    import static org.springframework.test.web.server.result.MockMvcResultMatchers.status;

    import org.junit.Before;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.http.MediaType;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    import org.springframework.test.web.server.MockMvc;
    import org.springframework.test.web.server.setup.MockMvcBuilders;


    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
    public class DemoApplicationTests {

        final String BASE_URL = "http://localhost:8080/";

        @Autowired
        private HelloWorld controllerToTest;

        private MockMvc mockMvc;

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

        @Test
        public void testSayHelloWorld() throws Exception{
            //Mocking Controller
            controllerToTest = mock(HelloWorld.class);

             this.mockMvc.perform(get("/")
                     .accept(MediaType.parseMediaType("application/json;charset=UTF-8")))
                     .andExpect(status().isOk())
                     .andExpect(content().mimeType(MediaType.APPLICATION_JSON));
        }

        @Test
        public void contextLoads() {
        }

    }

还有@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)可用。
petertc

当我在控制器中有自动接线的服务字段时,这对我有用,我尝试使用geoand的方式,但是服务字段始终为null,不知道为什么?

1
但是,这将启动整个上下文,并且这也是集成测试,而不是单元测试。
迈克尔

5

在您的DemoApplicationTests类中添加@WebAppConfigurationorg.springframework.test.context.web.WebAppConfiguration)批注将起作用。


0

假设我有一个RestControllerwithGET/POST/PUT/DELETE操作,我必须使用spring boot编写单元测试。我将只共享RestController类的代码和相应的单元测试。不会与控制器共享任何其他相关代码,可以对此进行假设。

@RestController
@RequestMapping(value = “/myapi/myApp” , produces = {"application/json"})
public class AppController {


    @Autowired
    private AppService service;

    @GetMapping
    public MyAppResponse<AppEntity> get() throws Exception {

        MyAppResponse<AppEntity> response = new MyAppResponse<AppEntity>();
        service.getApp().stream().forEach(x -> response.addData(x));    
        return response;
    }


    @PostMapping
    public ResponseEntity<HttpStatus> create(@RequestBody AppRequest request) throws Exception {
        //Validation code       
        service.createApp(request);

        return ResponseEntity.ok(HttpStatus.OK);
    }

    @PutMapping
    public ResponseEntity<HttpStatus> update(@RequestBody IDMSRequest request) throws Exception {

        //Validation code
        service.updateApp(request);

        return ResponseEntity.ok(HttpStatus.OK);
    }

    @DeleteMapping
    public ResponseEntity<HttpStatus> delete(@RequestBody AppRequest request) throws Exception {

        //Validation        
        service.deleteApp(request.id);

        return ResponseEntity.ok(HttpStatus.OK);
    }

}

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = Main.class)
@WebAppConfiguration
public abstract class BaseTest {
   protected MockMvc mvc;
   @Autowired
   WebApplicationContext webApplicationContext;

   protected void setUp() {
      mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
   }
   protected String mapToJson(Object obj) throws JsonProcessingException {
      ObjectMapper objectMapper = new ObjectMapper();
      return objectMapper.writeValueAsString(obj);
   }
   protected <T> T mapFromJson(String json, Class<T> clazz)
      throws JsonParseException, JsonMappingException, IOException {

      ObjectMapper objectMapper = new ObjectMapper();
      return objectMapper.readValue(json, clazz);
   }
}

public class AppControllerTest extends BaseTest {

    @MockBean
    private IIdmsService service;

    private static final String URI = "/myapi/myApp";


   @Override
   @Before
   public void setUp() {
      super.setUp();
   }

   @Test
   public void testGet() throws Exception {
       AppEntity entity = new AppEntity();
      List<AppEntity> dataList = new ArrayList<AppEntity>();
      AppResponse<AppEntity> dataResponse = new AppResponse<AppEntity>();
      entity.setId(1);
      entity.setCreated_at("2020-02-21 17:01:38.717863");
      entity.setCreated_by(“Abhinav Kr”);
      entity.setModified_at("2020-02-24 17:01:38.717863");
      entity.setModified_by(“Jyoti”);
            dataList.add(entity);

      dataResponse.setData(dataList);

      Mockito.when(service.getApp()).thenReturn(dataList);

      RequestBuilder requestBuilder =  MockMvcRequestBuilders.get(URI)
                 .accept(MediaType.APPLICATION_JSON);

        MvcResult mvcResult = mvc.perform(requestBuilder).andReturn();
        MockHttpServletResponse response = mvcResult.getResponse();

        String expectedJson = this.mapToJson(dataResponse);
        String outputInJson = mvcResult.getResponse().getContentAsString();

        assertEquals(HttpStatus.OK.value(), response.getStatus());
        assertEquals(expectedJson, outputInJson);
   }

   @Test
   public void testCreate() throws Exception {

       AppRequest request = new AppRequest();
       request.createdBy = 1;
       request.AppFullName = “My App”;
       request.appTimezone = “India”;

       String inputInJson = this.mapToJson(request);
       Mockito.doNothing().when(service).createApp(Mockito.any(AppRequest.class));
       service.createApp(request);
       Mockito.verify(service, Mockito.times(1)).createApp(request);

       RequestBuilder requestBuilder =  MockMvcRequestBuilders.post(URI)
                                                             .accept(MediaType.APPLICATION_JSON).content(inputInJson)
                                                             .contentType(MediaType.APPLICATION_JSON);

       MvcResult mvcResult = mvc.perform(requestBuilder).andReturn();
       MockHttpServletResponse response = mvcResult.getResponse();
       assertEquals(HttpStatus.OK.value(), response.getStatus());

   }

   @Test
   public void testUpdate() throws Exception {

       AppRequest request = new AppRequest();
       request.id = 1;
       request.modifiedBy = 1;
        request.AppFullName = “My App”;
       request.appTimezone = “Bharat”;

       String inputInJson = this.mapToJson(request);
       Mockito.doNothing().when(service).updateApp(Mockito.any(AppRequest.class));
       service.updateApp(request);
       Mockito.verify(service, Mockito.times(1)).updateApp(request);

       RequestBuilder requestBuilder =  MockMvcRequestBuilders.put(URI)
                                                             .accept(MediaType.APPLICATION_JSON).content(inputInJson)
                                                             .contentType(MediaType.APPLICATION_JSON);

       MvcResult mvcResult = mvc.perform(requestBuilder).andReturn();
       MockHttpServletResponse response = mvcResult.getResponse();
       assertEquals(HttpStatus.OK.value(), response.getStatus());

   }

   @Test
   public void testDelete() throws Exception {

       AppRequest request = new AppRequest();
       request.id = 1;

       String inputInJson = this.mapToJson(request);
       Mockito.doNothing().when(service).deleteApp(Mockito.any(Integer.class));
       service.deleteApp(request.id);
       Mockito.verify(service, Mockito.times(1)).deleteApp(request.id);

       RequestBuilder requestBuilder =  MockMvcRequestBuilders.delete(URI)
                                                             .accept(MediaType.APPLICATION_JSON).content(inputInJson)
                                                             .contentType(MediaType.APPLICATION_JSON);

       MvcResult mvcResult = mvc.perform(requestBuilder).andReturn();
       MockHttpServletResponse response = mvcResult.getResponse();
       assertEquals(HttpStatus.OK.value(), response.getStatus());

   }

}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.