Java Spring控制器单元测试最佳实践
在我的应用程序中,我有一个基于给定模式的控制器:Java Spring控制器单元测试最佳实践,java,spring,unit-testing,mockito,Java,Spring,Unit Testing,Mockito,在我的应用程序中,我有一个基于给定模式的控制器: public class Controller { @Autowired Mapper mapper; @Autowired Service service; public EntityDto create(EntityDto dto) { Entity entity = mapper.mapToEntity(dto); Entity saved = service.save(entity); return mapp
public class Controller {
@Autowired
Mapper mapper;
@Autowired
Service service;
public EntityDto create(EntityDto dto) {
Entity entity = mapper.mapToEntity(dto);
Entity saved = service.save(entity);
return mapper.mapToDto(saved);
}
这样测试类的好方法是什么?我看到了一些可能性:
以上任何一个都可以吗?也许有别的办法 我假设您使用的是Spring boot应用程序,您可以在测试包下编写测试类,如下所示:
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = AppConfig.class)
public class TestController {
@Autowired
Controller controller;
@Test
public void test() {
fail("Not yet implemented");
}
@Test
public void testGroupAlert() throws EntityNotFoundException, Exception {
Entitydto dto = new Entitydto() //Initialize your Entitydto object
controller.create(dto);
}
我经常看到的方法如下:
- 创建控制器的开发人员使用 嘲笑李>
- QA团队使用Spring上下文(和工具)创建集成测试 喜欢)
请记住,一个好的测试并不是覆盖最多的测试,而是覆盖最好的测试。您的控制器没有业务逻辑,但它接受并生成一些JSON(或其他数据),这些数据将被其他服务(即前端)使用 这就是为什么检查API合同是值得的 为此,您可以使用框架 用Mockito模拟一切,并检查从一个模拟中检索到的对象是否传递给另一个 通常,您会将逻辑提取到解释其功能的方法中。如果该方法使用另一个方法调用的结果,那么我将模拟该方法调用并使其返回一个固定值 在运行Spring上下文的情况下进行集成测试 您当然应该有一个测试来验证您的spring上下文(您可能只需要一个) 跳过控制器的测试,因为它不包含业务日志 您的控制器不应该包含业务逻辑,而是应该委托给执行某些工作的服务。Spring有MockMvc,它允许您一次测试一个控制器(并在Spring上下文中模拟其他bean)。这为一些有趣和有用的测试提供了很多机会 - 我更喜欢模拟外部系统。我喜欢测试服务中的控制器和逻辑。使用MockMvc,我还可以验证http响应 假设我有一个三层应用程序:
Controller->Service->Repository(模拟这个)
使用MockMvc进行的示例测试:
@ExtendWith(SpringExtension.class)
@WebMvcTest(controllers = {CategoryController.class})
public class CategoryControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private CategoryRepository repository; // mock database, but validate logic in the service-class
@Test
public void create_new_catgory() throws Exception {
var category = new Category("test");
given(repository.save(any())).willReturn(category);
mockMvc.perform(post("/category")
.content(toJson(new CategoryRequestDto("")))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().is2xxSuccessful())
.andExpect(content().json(toJson(category)));
verify(repository, times(1)).save(any());
}
}
更像是集成测试的测试示例(在mem数据库中运行):
1和2
我认为您应该在所有层中使用mocking库测试所有类。即使是最简单的类也可以进行测试
然后您应该进行集成测试
我认为集成测试也可以一直运行,如果它们不需要任何外部系统工作的话。唯一能做的就是时间。我还没有参与过这样的项目,因为等待集成测试的时间太长了
内存中的数据库和集成测试库/框架(例如@SpringBootTest)对于集成测试非常有用
我认为好的测试可以进行很多重构。如果您进行重构,集成测试不会中断太多。JUnit测试可能会失败,但我认为这不是问题。您应该始终拥有尽可能简单和干净的代码和测试
你最终得到了大量的快速单元测试,没有那么多的高级测试一起检查,没有模拟框架。但这实际上是一种集成测试,而不是单元测试。如果你真的想进行单元测试,为什么要使用字段注入?其关联性如何?:)我也经常看到这一点,但在IMO中,这种方法与生产代码的耦合太多。在这种情况下,在不破坏测试的情况下进行重构是不可能的,而且这也是单元测试的主要目标之一。使用这种方法,测试将不支持重构,因为每一行代码的更改都会破坏测试。1和2一起。你得到了一些答案,它们都不够好吗?请详细说明这个问题。例如,您的控制器应该具有与选项相关的注释,如GetMapping、RestController等:1。模拟服务,但不模拟映射器(应该有自己的unittest)&2始终有一个测试来验证有效的Spring上下文。3.Spring团队创建MockMvc是为了测试控制器。我建议使用它
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = {ShoprApplication.class, TestConfig.class})
@Transactional
public class ProductControllerTest {
@Autowired TestRestTemplate template;
@Autowired EntityManagerFactory entityManagerFactory;
private TestEntityManager em;
@BeforeEach
public void configure() {
em = new TestEntityManager(entityManagerFactory);
}
@Test
public void createProduct() {
var response = template.postForEntity(
"/product",
new Product("Apples", 15.00, new Category("Fruit"), 6),
Long.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertTrue(response.getBody() != null && response.getBody().intValue() > 0L);
var product = em.getEntityManager().find(Product.class, response.getBody());
assertNotNull(product);
assertEquals("Apples", product.getName());
}