如何测试使用 JDBC 的 Spring 控制器?

How can I test a Spring controller that uses JDBC?

我有一个 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 方法一样对其进行测试,或者使用 MockMvc 对相关路径执行获取请求。

如果你把你的方法的主体贴出来就好了(现在很难帮忙)。

我根本不是测试控制器的最佳人选(我也是不久前开始的) 好吧,我模拟了我的控制器。

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 内容并将其发送到我的模拟。我检查最终状态以查看它是否创建了用户。

Do I need to mock something?

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

Can I just test it like a plain Java method (call it with whatever parameters and assert the output)?

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

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

制作一个使用 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"));
    }

}

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

@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());
    }
}