我没有测试经验,尝试通过单元测试来测试一个方法。我看到的所有示例都使用mock值执行操作。我知道,我还将在mockito
中使用我在项目中使用的mock值。这是我想测试的服务方法:
ProductServiceImpl:
public List<ProductDTO> findAllByCategoryUuid(UUID categoryUuid) {
// code omitted
return result;
}
这是我的单元测试课程:
ProductServiceImplTest:
// ? @InjectMocks
@Autowired
ProductServiceImpl productService;
@Mock
ProductRepository productRepository;
@Test
public void testFindAllByCategoryUuid() {
UUID categoryUuid = UUID.randomUUID();
final List<Product> productList = new ArrayList<>();
for (int i = 0; i < size; i++) {
// create product by setting "categoryUuid" and add to productList
}
productRepository.saveAll(productList);
when(productService.findAllByCategoryUuid(categoryUuid)
.thenReturn(productList);
}
我的问题:
1.为了测试服务方法,上述方法是否正确?我认为我不应该处理内部服务方法,只通过categoryUuid
并检查该方法的结果进行测试?这是真的吗?
2.在测试类中,我使用@Autowired
访问服务方法,但我不确定是否应该使用@Mock
。有什么错误吗?
如有任何帮助,我们将不胜感激。
更新:我还使用DiffBlue插件创建了单元测试,它生成了一个测试方法,如下所示。但我认为这似乎是在测试存储库方法,而不是服务方法。不是吗?
@Test
public void testFindAllByCategoryUuid() {
when(this.productRepository.findAllByCategoryUuid((UUID) any()))
.thenReturn(new ArrayList<Product>());
assertTrue(this.productService.findAllByCategoryUuid(UUID.randomUUID())
.isEmpty());
verify(this.productRepository).findAllByCategoryUuid((UUID) any());
// ...
}
我不是专家,但我会尽力回答你的问题
单元测试方法的一般方法应该是针对所有可能的输入集测试输出。在您的特定情况下,您可以测试
- 输入:现有UUID输出:非空列表
- 输入:不存在UUID输出:空列表
- input:null:空列表
现在您在这里所做的是正确的,您需要自动连接正在为其编写测试用例的类,并模拟该类中的依赖关系。唯一错误的是
when(productService.findAllByCategoryUuid(categoryUuid)
.thenReturn(productList);
应该是
when(productRepository.findAllByCategoryUuid(categoryUuid)
.thenReturn(productList);
在这里,您正在嘲笑productRepository.findAllByCategoryUuid,因为您的目标是在服务类中测试该方法。
在这之后,只需为上面提到的所有条件添加适当的断言语句。
此外,每当针对某些代码记录错误时,我通常会遵循一条规则,我会在Junit中使用assert来覆盖输入和输出情况,这样每次我都会测试所有可能的输入和输出场景。
针对服务层编写单元测试有缺点:
- 您违反了测试中方法的封装。如果它因为您开始调用不同的类/方法而发生变化,那么测试就会中断。即使该方法可能工作正常
- 因为您打算使用mocking,所以部分测试将只是检查您的mock是否设置为通过测试。所以基本上你将测试测试逻辑
通常将逻辑向下移动(例如,移动到模型(会更有效率。然后可以在没有mock的情况下对这些类进行单元测试。然后,您可以编写一个更高级别的测试(包括DB(,检查所有内容是否协同工作。
读数:
- 贫血架构-测试的敌人
- 构建测试金字塔以优化自动化测试
使用Mockito 编写Junit测试时需要记住的重要事项
- 所有类级别@Runwith((
- 测试类应该使用@InjectMocks
- 所有测试都应标注@Test
- 任何外部服务都应使用@Mock进行模拟
- 任何对DB或其他服务的调用都应该被模拟,并相应地返回值
- 您应该使用断言来测试结果
我会写这样的东西:
@RunWith(MockitoJUnitRunner.class)
public class ProductServiceImplTest {
@InjectMocks
ProductServiceImpl productService;
@Mock
ProductRepository productRepository;
@Test
public void testFindAllByCategoryUuid() {
UUID categoryUuid = UUID.randomUUID();
final List<Product> productList = new ArrayList<>();
for (int i = 0; i < size; i++) {
// create product by setting "categoryUuid" and add to productList
}
when(productRepository.saveAll(ArgumentMatchers.any()).thenReturn(productList);
// Or below might work for newer version of test cases when we get Null Pointer Exp using older version of Junit test cases
//doReturn(productList).when(productRepository).saveAll(any(List.class));
List<ProductDTO> response = productService.findAllByCategoryUuid(categoryUuid);
Assert.assertNotNull(response);
Assert.assertEquals("Object Value", response.getXXX());
}