【问题标题】:mocking parent internal dependency模拟父级内部依赖
【发布时间】:2019-08-24 12:19:36
【问题描述】:

我想为服务方法编写单元测试,但我的服务是以通用方式编写的。我的服务扩展了通用服务,在通用服务中,我们有自定义通用存储库依赖项和通用存储库。

public class MyService extends GenericService<MyService, Long> {
    private final MyServiceRepository myServiceRepo;
    private final AnotherRepository anotherRepository;

    @Autowired
    public MyService(MyServiceRepository myServiceRepo, AnotherRepository anotherRepository) 
    {
         super(myServiceRepo , MyEntity.class);
         this.myServiceRepo= myServiceRepo;
         this.anotherRepository = anotherRepository ;
    }

    // specific custom service method for MyEntity service

}

================================================ ===============

public class GenericService<T,ID extends Serializable>{

    @Autowired
    private CustomGenericRepository customRepository;

    private GenericRepository<T, ID> genericRepository;

    public GenericService(GenericRepository<T, ID> genericRepository , Class entity) {
        this.genericRepository = genericRepository;
        this.entity = entity;
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public Response add(T t) {

        Optional o= genericRepository.findById(id); 

        if(o!=null){
            t1 = genericRepository.save(t);
            return "adding data is succussfully" 
        }else{
             throw new RecordAlreadyExistException(entityClass, "id", getId(t));
        }
     } 
}

在此代码中GenericRepository 继承JpaRepository

我的测试课是:

@RunWith(MockitoJUnitRunner.class)
@SpringBootTest
public class GroupServiceTest {

    @Mock
    private MyEntityRepository repo;
    @Mock
    private AnotherRepository anotherRepo;
    @Mock
    private CustomGenericRepository  customGenericRepository;
    @Mock
    private GenericRepository<MyEntity,Long> genericRepository ;
    @InjectMocks
    private MyService service;

    @Test
    public void testServiceAddMethod(){
        MyEntity data = new MyEntity();
        data.setId(1L);
        Mockito.when(repo.save(data)).thenReturn(data);
            Mockito.when(genericRepository.findById(1L)).thenReturn(optional.empty());
        service.add(data);
        Mockito.verify(repo,Mockito.times(1)).save(data);
    }
}

我使用这个 maven 依赖:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.4.RELEASE</version>
</parent>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-module-junit4</artifactId>
    <version>1.7.3</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-api-mockito</artifactId>
    <version>1.7.3</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>1.10.19</version>
</dependency>    

我现在应该如何解决这个问题?有没有办法模拟GenericRepository

【问题讨论】:

    标签: unit-testing spring-boot nullpointerexception mocking mockito


    【解决方案1】:

    您必须至少重构一些代码才能使该测试正常工作。

    1. 您尝试将 Mocks 注入 MyService 类。

      但是,您的类有一个带有两个参数的构造函数:

      • MyServiceRepository myServiceRepo
      • 另一个存储库另一个存储库


      这意味着 Mockito 将使用Constructor Injection,这将您限制为仅注入这两个字段。 CustomGenericRepository 不会被注入。


    2. 两者都不是MyEntityRepository。但是,这门课似乎与您的测试课无关?

    3. 另外,您的NullPointerException 来自哪里也不是很清楚。
      (考虑发布完整堆栈跟踪)。

      我假设它来自这条线
      Mockito.when(genericRepository.findById(1L)).thenReturn(optional);

      optional 返回时为null,因此在调用该方法时,您对其调用get 方法,从而导致NPE。您需要将其更改为 Optional.empty()

      genericRepository.findById(id) 应该返回Optional&lt;MyEntity&gt; 的假设下,您将不得不更改您的代码,因为您不会从中得到null。相反,您的代码应如下所示:
    Optional<T> t1 = genericRepository.findById(id);
    if (t1.isEmpty()) {
      ...
    } else {
      ...
    }
    


    1. 除此之外,对于add 方法的简单测试,您只需为genericRepositry 注入一个模拟(与myServiceRepo 相同)。但是我不确定您要做什么以及代码是否完整,所以我还是决定指出以前的问题。

    (我假设 datadto 是相同的,只是你的错字)

    @RunWith(MockitoJUnitRunner.class)
    public class GroupServiceTest {
    
        @Test
        public void testServiceAddMethod() {
    
            GenericRepository genericRepository = Mockito.mock(GenericRepository.class);
            AnotherRepository anotherRepository = Mockito.mock(AnotherRepository.class);
    
            MyEntity data = new MyEntity();
            data.setId(1L);
    
            Mockito.when(genericRepository.save(data)).thenReturn(data);
            Mockito.when(genericRepository.findById(1L)).thenReturn(Optional.empty());
    
            MyService service = new MyService(genericRepository, anotherRepository);
            service.add(data);
    
            Mockito.verify(genericRepository, Mockito.times(1)).save(data);
        }
    }
    

    所以如果你只是调整Optional 的处理,这个测试应该可以。 (而且您可能想要更改 add 方法的返回值,但我认为这只是占位符代码)

    您的MyEntity 还应该正确实现equalshashCode 方法,以便java 将这些对象识别为相同。

    @SpringBootTest 似乎不需要此单元测试。

    请注意,为了简单(和更好的可读性),我决定注入 Mocks 手册,如果您只是使用它也应该可以工作 (在这种情况下,所有其他依赖项都将为空):

    @Mock
    GenericRepository<MyEntity,Long> myServiceRepo;
    
    @InjectMocks
    MyService service;
    
    

    【讨论】:

    • 嗨。感谢您的回答。但我必须回答你的一些模棱两可的问题。 1-此代码是原始代码的一部分,因冗长的问题而被缩短。 2-我知道构造函数注入,但由于我必须在构造函数中获得另一个数量,所以我不能使用它。 3-这个版本的 mockito 没有 MockitoMock 。 3-java.util.Optional 在没有值且没有 isEmpty() 时返回 NoSuchElementException 错误。 4-MyEntityRepository 用于我的自定义查询,因为父类在多个实体之间共享。
    • 我更新了我的代码并解决了我的问题。感谢@second 您解释的提示
    • MockitoMock 是一个错字,Optional.isEmpty() 是在 Java 11 中添加的,如果您使用旧版本 isPresent() == false 就可以。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-05
    • 1970-01-01
    • 2011-05-13
    • 1970-01-01
    相关资源
    最近更新 更多