在 Spring Boot 应用程序上为我的服务实现生成单元测试

问题描述 投票:0回答:3

经过几个小时的尝试和失败后,我来找你,希望能找到解决方案。 我正在努力为我的 Spring Boot 应用程序进行单元测试。我正在使用mockito和Junit 5。

我的架构是这样的:

  • 一个控制器
  • 服务的接口
  • 服务接口的实现
  • 扩展 CrudRepository 的存储库

现在我只想测试我的服务实现。

这就是现在的样子:

`

@SpringBootTest public class ServiceImplTest{
        @Mock    
     private Entity e;


     @MockBean
     private EntityRepository entityRepository;
        
     @MockBean
     private EntityService entityService;
    
     @BeforeEach
         init(){
               e = new Entity();
               e.name ="abc";
          }
    
    
    
    @Test
     private simpleTest(){
        // saving my element in the mocked repository
        entityRepository.save(e);
    
    
        // I have a repository query to delete an element in a specific way. I ask it to return 1 if it receives the order to activate this method
        doReturn(1).when(entityRepository).specialDeleteEntity(1L);
    
    
       // in the code serviceDeleteEntity() does some operations then calls entityRepository.specialDeleteEntity
        int howMany = entityService.serviceDeleteEntity(1L);
    
    
         // this fails because there was nothing in the repository to be deleted 
         assertEquals(howMany, 1);
    
     }
}

我只是有一种感觉,模拟存储库没有连接到我的模拟服务,因此它们之间的操作不起作用。

我还尝试了另一种解决方案,我没有模拟存储库,以防万一:

@SpringBootTest class ServiceImplTest {
    @MockBean
    private EntityRepository mockEntityRepository;
    
    @Autowired
    private EntityService entityService;
    
    
    @Test
    void testDelete() {
        // Given
        final Entity entity = new Entity();
        entity.name = "abc";
    
        // Setup
        when(mockEntityRepository.specialDeleteEntity(1L)).thenReturn(1);
    
        // When
        final int result = entityService.specialDeleteEntity(1L);
    
        // Then
        assertThat(result).isEqualTo(1);
        verify(mockEntityRepository).specialDeleteEntity(1L);
    }
}

我可能缺少一些注释或一些方法。我只想得到你对这个问题的建议,也许是解决问题的一步。非常感谢。

java spring-boot junit spring-test
3个回答
1
投票

您的测试存在一些问题:

1.错误使用模拟

模拟是类的虚拟实现。您可以使用它们来绕过这些类的正常行为。例如,如果您为

EntityService
编写单元测试,您不想设置整个数据库并插入/删除模拟数据。因此,在这种情况下,您需要模拟依赖关系,例如
EntityRepository

这意味着在你的

EntityServiceTest
中你应该只是嘲笑
EntityRepository
。您不应该模拟
Entity
,因为此类通常不包含很多行为,并且您不应该模拟
EntityService
,因为您想测试此服务的实际行为而不是模拟的行为。

2.选择一个模拟框架

您的测试当前使用

@Mock
@MockBean
的组合。 这两个注释都允许您模拟一个类。 不同之处在于
@Mock
仅依赖于 Mockito,而
@MockBean
模拟该类并创建它的 Spring bean 以用于自动装配。

这意味着

@MockBean
注释仅在您运行 Spring Boot 应用程序(的一部分)时才起作用。 这就是您使用
@SpringBootTest
注释的原因。 缺点是,这可能需要额外的配置,并由于启动/停止应用程序而减慢测试速度。

如果您只需要使用一些模拟对单个类进行单元测试,则仅使用 Mockito 编写测试会更容易。 为此,请使用

@Mock
注释并将
@SpringBootTest
替换为
@ExtendWith(MockitoExtension.class)

// Replace @SpringBootTest with @ExtendWith to enable testing with Mockito
@ExtendWith(MockitoExtension.class)
class EntityServiceTest {
    // Add @InjectMocks to the class where the mocks should be injected into.
    // Normally this is the class that you want to test
    @InjectMocks
    private EntityService service;
    // Use @Mock in stead of @MockBean
    @Mock
    private EntityRepository repository;

    // ...
}

3.测试班级的行为

正如我之前提到的,模拟是类的虚拟实现。 因此,这意味着如果您调用

repository.save(..)
,它实际上不会执行任何操作,因为它背后没有任何行为,也没有数据库。

您真正想要测试的是

service.serviceDeleteEntity()
方法是否使用正确的参数调用
repository.specialDeleteEntity()
方法。

这意味着您的测试的第二个示例是正确的方法。 您唯一不需要的是

entity
,因为您的测试不依赖它(假设您的服务将 id 参数传递给存储库并返回查询结果):

@Test
void testDelete() {
    // Given
    when(mockEntityRepository.specialDeleteEntity(1L)).thenReturn(1);
    
    // When
    final int result = entityService.specialDeleteEntity(1L);
    
    // Then
    assertThat(result).isEqualTo(1);
    verify(mockEntityRepository).specialDeleteEntity(1L);
}

0
投票

感谢@g00glen00b提供解决方案。

@ExtendWith(MockitoExtension.class)
public class EntityServiceImplTest {
    @InjectMocks
    private EntityServiceImpl entityServiceImpl;
    @Mock
    private EntityRepository entityRepository;

 
@Test{
   Entity e = new Entity();

   when(entityRepository.getEntityById(1L)).thenReturn(e);

   final Entity result = entityServiceImpl.getEntityById(1L);

   assertEquals(e, result);  
  }

}

这里的问题是我一方面必须使用 MockitoExtension。

另一个问题是,在我的服务实现方法中,有一个隐藏的“new Timestamp(System.currentTimeMillis())”用于调用存储库中的方法。

所以我“承诺”的时间和调用服务实现期间创建的时间之间总是存在差异。 再次感谢您


-1
投票

我认为您必须将模拟存储库插入到您的服务中才能使用它: EntityService.setRepository(mockEntityRepository);

© www.soinside.com 2019 - 2024. All rights reserved.