【问题标题】:How can I test a Spring controller that uses JDBC?如何测试使用 JDBC 的 Spring 控制器?
【发布时间】:2020-07-10 13:48:47
【问题描述】:

我有一个如下所示的 Spring 控制器:

@RestController
@RequestMapping("/foo")
public class FooController {
    @Autowired
    private NamedParameterJdbcTemplate template;

    @GetMapping("/{name}")
    public List<Foo> byName(@PathVariable String name) {
        Map<String, String> params = new HashMap<>();
        params.put("name", name);
        List<Foo> result = template.query("SELECT * FROM FOOS WHERE FOO_NM = :name", params, new FooRowMapper());
        if (result.size() == 0) {
            throw new ResponseStatusException(HttpStatus.NOT_FOUND, String.format("foo %s not found", name));
        }
        return result;
    }
}

但是,我不确定如何对此进行测试。我可以做一个基本的“Spring 可以设置”测试:

@SpringBootTest
public class FooControllerTest {
    @Autowired
    private FooController controller;

    @Test
    public void canCreate() {
        Assertions.assertNotNull(controller);
    }
}

但我不确定什么是正确的测试方法,例如byName 方法。我需要嘲笑什么吗?我可以像普通的 Java 方法一样测试它(使用任何参数调用它并断言输出)吗?

【问题讨论】:

    标签: java spring spring-boot-test


    【解决方案1】:

    我需要模拟一些东西吗?

    这取决于您究竟需要测试什么。 如果你编写一个单元测试——那么你通常会像你假设的那样模拟外部依赖项,它们会遵循一些约定。

    我可以像普通的 Java 方法一样测试它吗(使用任何参数调用它并断言输出)?

    当然可以,但这意味着您正在测试一个 bean 方法,而不是从接收 http 请求和返回响应的完整流程。

    如果您需要进行端到端测试,则需要运行您的应用,然后调用您的 HTTP 方法并验证响应。为此,您可以使用MockMvc,如答案之一所述。

    【讨论】:

      【解决方案2】:

      制作一个使用 JDBC 本身的控制器是个坏主意。 最好使用Controller-Service-Repository pattern。

      所以你在 Foo 控制器中的代码可以是这样的:

      @RestController
      @RequestMapping("/foo")
      public class FooController {
        @Autowired
        private FooService fooService;
        
        @GetMapping("/{name}")
        public ResponseEntity<?> byName(@PathVariable String name) {
          final List<Foo> list = fooService.getFooByName(name);
          return ResponseEntity.ok(list);
        }
      }
      

      出于测试目的,最好使用 MockMvc:

      // In the Foo controller
      @GetMapping("/{name}")
          public ResponseEntity<?> byName(@PathVariable String name) {
              return ResponseEntity.ok("Hello, " + name);
      }
      
      // In the Test class
      @SpringBootTest
      @AutoConfigureMockMvc
      class ControllerTests {
          @Autowired
          private MockMvc mockMvc;
      
          @Autowired
          private FooController fooController;
      
          @Test
          public void contextLoads() {
              assertThat(fooController, is(notNullValue()));
          }
      
          @Test
          public void getUser() throws Exception {
              MockHttpServletResponse response = mockMvc.perform(MockMvcRequestBuilders.get("/foo/Joe"))
                      .andExpect(status().isOk())
                      .andDo(print())
                      .andReturn().getResponse();
      
              assertThat(response.getContentAsString(), is("Hello, Joe"));
          }
      
      }
      

      【讨论】:

        【解决方案3】:

        我并不是最擅长测试我的控制器(我也是不久前开始的) 好吧,我模拟了我的控制器。

        class UserControllerTest {
        
        @Autowired
        private MockMvc mockMvc;
        
        @Test
        void addUserTest() throws Exception {
            String content = "{" +
                    "\"username\": \"test\"," +
                    "\"password\": \"tesT1234\"," +
                    "\"email\": \"test@test.com\"" +
                    "}";
        
            mockMvc.perform(
                    MockMvcRequestBuilders.post("/users/signup")
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(content))
                    .andExpect(status().isCreated());
        }
        

        例如,对于注册用户,我构建了一个 JSON 内容并将其发送到我的模拟。我检查最终状态以查看它是否创建了用户。

        【讨论】:

          【解决方案4】:

          您可以像常规 Java 方法一样对其进行测试,也可以使用 MockMvc 对相关路径执行 get 请求。

          如果您发布您的方法的主体会很好(现在很难提供帮助)。

          【讨论】:

          • 我已经添加了方法体,希望对您有帮助。
          • 正如我所说:学习一点 MockMvc (这很简单)或者只是像普通方法一样测试它。
          【解决方案5】:

          经过一些研究和发布答案的帮助,我将检索代码移到了服务中:

          @Service
          public class FooService implements IFooService {
              @Autowired
              private NamedParameterJdbcTemplate template;
          
              // the only method of IFooService
              @Override
              public List<FormulaFunction> getAllByName(String name) {
                  Map<String, String> params = new HashMap<>();
                  params.put("name", name);
                  return template.query("SELECT * FROM FOOS WHERE FOO_NM = :name", params, new FooRowMapper());
              }
          }
          

          在测试控制器时,使用 Mockito 模拟该服务:

          @WebMvcTest(FooController.class)
          public class FooControllerTest {
              @MockBean
              private IFooService service;
          
              @Autowired
              private FooController controller;
          
              @Autowired
              private MockMvc mvc;
          
              @Test
              public void canCreate() {
                  Assertions.assertNotNull(controller);
              }
          
              /**
               * Test that controller properly returns 404 when no results are found
               */
              @Test
              public void test404() throws Exception {
                  doReturn(new ArrayList<>()).when(service).getAllByName("bars");
                  this.mvc.perform(get("/foo/bars")).andExpect(status().isNotFound());
              }
          }
          

          【讨论】:

            猜你喜欢
            • 2018-08-12
            • 1970-01-01
            • 2013-02-19
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2017-12-05
            • 1970-01-01
            • 2021-02-08
            相关资源
            最近更新 更多