通过单元测试测试服务方法?

时间:2021-06-27 18:42:15

标签: java spring spring-boot unit-testing testing

我没有测试经验,尝试通过单元测试来测试方法。我看到的所有示例都通过使用模拟值执行操作。我知道,我还将在我的项目中使用带有 mockito 的模拟值。这是我要测试的服务方法:

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

3 个答案:

答案 0 :(得分:2)

我不是专家,但我会尽力回答你的问题

对您的方法进行单元测试的一般方法应该是针对所有可能的输入集测试输出。 在您的具体情况下,您可以测试

  • 输入:现有 UUID 输出:非空列表。
  • 输入:不存在的 UUID 输出:空列表。
  • input: null : 空列表。

现在您所做的一切是正确的,您需要自动装配您正在为其编写测试用例的类并模拟该类中的依赖项。 唯一的错误是

when(productService.findAllByCategoryUuid(categoryUuid)
                .thenReturn(productList);

应该

when(productRepository.findAllByCategoryUuid(categoryUuid)
                .thenReturn(productList);   

这里是在模拟 productRepository.findAllByCategoryUuid,因为您的目标是测试服务类中的方法。

在此之后,只需为上述所有条件添加适当的断言语句。

此外,每当针对某些代码记录错误时,我通常都会遵循一条规则,我尝试在我的 Junit 中使用断言覆盖该输入和输出情况,以便每次我都会测试所有可能的输入和输出情况。

答案 1 :(得分:2)

使用 Mockito 编写 Junit 测试时要记住的重要事项

  1. 所有班级@Runwith()
  2. 测试类应该使用@InjectMocks
  3. 所有测试都应使用@Test 进行注释
  4. 任何外部服务都应该使用@Mock 进行模拟
  5. 任何对数据库或其他服务的调用都应该被模拟并相应地返回值。
  6. 你应该有断言来测试你的结果。

我会这样写:

@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); 
    
    List<ProductDTO> response = productService.findAllByCategoryUuid(categoryUuid);
    
    Assert.assertNotNull(response);
    Assert.assertEquals("Object Value", response.getXXX());
}

答案 2 :(得分:1)

针对服务层编写单元测试有缺点:

  1. 您违反了被测方法的封装。如果它因为您开始调用不同的类/方法而发生变化 - 测试将中断。即使该方法可能正常工作。
  2. 因为您打算使用模拟,所以您的部分测试将只是检查您的模拟是否设置为通过测试。所以基本上您将测试测试逻辑。

将逻辑向下移动通常更有效率,例如到模型。然后可以在没有模拟的情况下对这些类进行单元测试。然后您可以编写一个更高级别的测试(包括 DB)来检查一切是否正常工作。

阅读: