【问题标题】:How to write a unit test for a Spring Boot Controller endpoint如何为 Spring Boot Controller 端点编写单元测试
【发布时间】:2015-05-17 05:14:35
【问题描述】:

我有一个带有以下内容的示例 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() {
    }

}

【问题讨论】:

  • 尝试用@WebAppConfiguration 注释DemoApplication。如果这不起作用,您可以添加它的代码吗?

标签: java unit-testing junit spring-boot spring-test-mvc


【解决方案1】:

在 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"));
    }
    
}

有关详细信息,请参阅 this 博客文章以及 documentation

【讨论】:

  • 这不起作用。您仍然会收到 java.lang.IllegalStateException: Unable to find a @SpringBootConfiguration, you need to use @ContextConfiguration or @SpringBootTest(classes=...) with your test 错误。
  • 这适用于spring-boot 1.4.0junit
  • 我也没有发现任何问题......我会很高兴有一个可重现的错误案例
  • @Lucas 你得到的错误是由于spring-boot用于搜索上下文初始化器的算法,你需要一个应用程序类在测试类的包中,或者它的子包中,标记为通过@SpringBootApplication@SpringBootConfiguration,参考:github.com/spring-projects/spring-boot/issues/5987
  • 添加静态导入会很好: 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;
【解决方案2】:

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

通过注册一个或多个 @Controller 的实例来构建一个 MockMvc 和 以编程方式配置 Spring MVC 基础结构。这允许 完全控制控制器的实例化和初始化, 及其依赖项,类似于普通的单元测试,同时也使 一次可以测试一个控制器。

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

public class DemoApplicationTests {

    private MockMvc mockMvc;

    @Before
    public void setup() {
        this.mockMvc = MockMvcBuilders.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);如果我证明退货,我如何确定它按预期工作?
  • 这个例子只是讨论了基本的期望,但你可以使用 Hamcrest 匹配器进一步深入,并断言身体中的任何东西,例如您期望的实际 json 响应。基本上,无论您期望 Employee 实例发生什么,您都可以断言,无论是转换为 JSON 还是 XML,重命名属性等。
  • .andExpect(content().contentType("application/json"));这一行给了我一个错误“方法内容()未定义类型”。
  • 对于上面的示例代码,您需要一些静态导入。对于content(),那就是org.springframework.test.web.servlet.result.MockMvcResultMatchers.content。顺便说一句:我建议使用像 MediaType.APPLICATION_JSONMediaType.APPLICATION_JSON_UTF8 这样的 MediaType 常量,而不是示例中使用的基于字符串的值。
【解决方案3】:

假设我有一个 RestControllerGET/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());

   }

}

【讨论】:

    【解决方案4】:

    这是另一个使用 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) 可用。
    • 当我在控制器中有一个自动装配的服务字段时,这对我有用,我尝试使用 geoand 的方式,但服务字段总是为空,不知道为什么?
    • 这会启动整个上下文,这也是一个集成测试,而不是单元测试。
    【解决方案5】:

    @WebAppConfiguration (org.springframework.test.context.web.WebAppConfiguration) 注释添加到您的 DemoApplicationTests 类将起作用。

    【讨论】:

      猜你喜欢
      • 2021-01-08
      • 1970-01-01
      • 2019-11-19
      • 2019-09-27
      • 1970-01-01
      • 2020-09-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多