经过几个小时的尝试和失败后,我来找你,希望能找到解决方案。 我正在努力为我的 Spring Boot 应用程序进行单元测试。我正在使用mockito和Junit 5。
我的架构是这样的:
现在我只想测试我的服务实现。
这就是现在的样子:
`
@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);
}
}
我可能缺少一些注释或一些方法。我只想得到你对这个问题的建议,也许是解决问题的一步。非常感谢。
您的测试存在一些问题:
模拟是类的虚拟实现。您可以使用它们来绕过这些类的正常行为。例如,如果您为
EntityService
编写单元测试,您不想设置整个数据库并插入/删除模拟数据。因此,在这种情况下,您需要模拟依赖关系,例如 EntityRepository
。
这意味着在你的
EntityServiceTest
中你应该只是嘲笑EntityRepository
。您不应该模拟 Entity
,因为此类通常不包含很多行为,并且您不应该模拟 EntityService
,因为您想测试此服务的实际行为而不是模拟的行为。
您的测试当前使用
@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;
// ...
}
正如我之前提到的,模拟是类的虚拟实现。 因此,这意味着如果您调用
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);
}
感谢@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())”用于调用存储库中的方法。
所以我“承诺”的时间和调用服务实现期间创建的时间之间总是存在差异。 再次感谢您
我认为您必须将模拟存储库插入到您的服务中才能使用它: EntityService.setRepository(mockEntityRepository);