Mockito WrongTypeOfReturnValue:findById() 无法返回布尔值

Mockito WrongTypeOfReturnValue: Boolean cannot be returned by findById()

我正在尝试使用 Mockito 通过 JUnit 测试来测试以下方法:

@Override public List<Adoption> search(String username, Integer id) {

List<Adoption> emptySearchResult = new ArrayList<>();

if(id != null && !username.equals("") ) {
    if(!this.petRepository.findById(id).isPresent()){
        return emptySearchResult;
    }
    if(!this.appUserRepository.findByUsername(username).isPresent()){
        return emptySearchResult;
    }
    Pet pet = this.petRepository.findById(id).orElseThrow( () -> new PetNotFoundException(id));
    AppUser user  = this.appUserRepository.findByUsername(username).orElseThrow( () -> new UsernameNotFoundException(username));
    return this.adoptionRepository.findAllByUserAndPet(user, pet);
}
else if(id != null && username.equals("")){
    if(!this.petRepository.findById(id).isPresent()){
        return emptySearchResult;
    }
    Pet pet = this.petRepository.findById(id).orElseThrow( () -> new PetNotFoundException(id));
    return this.adoptionRepository.findAllByPet(pet);
}
else if(id == null && !username.equals("")) {
    if(!this.appUserRepository.findByUsername(username).isPresent()){
        return emptySearchResult;
    }
    AppUser user  = this.appUserRepository.findByUsername(username).orElseThrow( () -> new UsernameNotFoundException(username));
    return this.adoptionRepository.findAllByUser(user);
}
else {
    return this.adoptionRepository.findAll();
}

}

但是,我 运行 遇到了以下部分的问题:

if(!this.petRepository.findById(id).isPresent())

尽管我嘲笑了 this.petRepository.findById(id),但由于某种原因 isPresent() 返回了 false。这是我的测试初始化​​:

@Mock
private AdoptionRepository adoptionRepository;

@Mock
private PetRepository petRepository;

@Mock
private AppUserRepository appUserRepository;

private AdoptionServiceImpl service;

private Adoption adoption1;
private Adoption adoption2;
private Adoption adoption3;

private AppUser user;
private AppUser user2;
private Pet pet;
private Pet petAlteadyAdopted;

List<Adoption> allAdoptions = new ArrayList<>();
List<Adoption> userFilteredAdoptions = new ArrayList<>();
List<Adoption> petFilteredAdoptions = new ArrayList<>();

@Before
public void init() {
    MockitoAnnotations.initMocks(this);
    user = new AppUser("username","name","lastname","email@gmail.com","pass",ZonedDateTime.now(), Role.ROLE_USER, City.Skopje);
    user2 = new AppUser("username1","name","lastname","email@gmail.com","pass",ZonedDateTime.now(), Role.ROLE_USER, City.Skopje);

    Center center = new Center("a", City.Bitola,"url");
    pet = new Pet("p", Type.DOG,"b", Gender.FEMALE,"d",center, ZonedDateTime.now(),"url",null,false,ZonedDateTime.now());
    petAlteadyAdopted = new Pet("p", Type.DOG,"b", Gender.FEMALE,"d",center, ZonedDateTime.now(),"url",null,true,ZonedDateTime.now());

    pet.setId(0);
    petAlteadyAdopted.setId(1);
    adoption1 = new Adoption(ZonedDateTime.now(),ZonedDateTime.now(),Status.ACTIVE,user,pet);
    adoption2 = new Adoption(ZonedDateTime.now(),ZonedDateTime.now(),Status.CLOSED,user,pet);
    adoption3 = new Adoption(ZonedDateTime.now(),ZonedDateTime.now(),Status.CLOSED,user2,new Pet());

    allAdoptions.add(adoption1);
    allAdoptions.add(adoption2);
    allAdoptions.add(adoption3);

    petFilteredAdoptions.add(adoption2);
    petFilteredAdoptions.add(adoption1);

    userFilteredAdoptions.add(adoption2);
    userFilteredAdoptions.add(adoption1);

    Mockito.when(this.adoptionRepository.findById(0)).thenReturn(java.util.Optional.of(adoption1));
    Mockito.when(this.adoptionRepository.findById(1)).thenReturn(java.util.Optional.of(adoption2));

    Mockito.when(this.petRepository.findById(0)).thenReturn(java.util.Optional.of(pet));
    Mockito.when(this.petRepository.findById(1)).thenReturn(java.util.Optional.of(petAlteadyAdopted));
    

    Mockito.when(this.appUserRepository.findByUsername("username")).thenReturn(java.util.Optional.of(user));
    Mockito.when(this.appUserRepository.findByUsername("username1")).thenReturn(java.util.Optional.of(user2));

    Mockito.when(this.adoptionRepository.findAll()).thenReturn(allAdoptions);
    Mockito.when(this.adoptionRepository.findAllByPet(pet)).thenReturn(petFilteredAdoptions);
    Mockito.when(this.adoptionRepository.findAllByUser(user)).thenReturn(userFilteredAdoptions);
    Mockito.when(this.adoptionRepository.findAllByUserAndPet(user,pet)).thenReturn(userFilteredAdoptions);

    Mockito.when(this.adoptionRepository.save(Mockito.any(Adoption.class))).thenReturn(adoption1);

    this.service = Mockito.spy(new AdoptionServiceImpl(this.adoptionRepository, this.petRepository,this.appUserRepository));
}

因此,以下测试失败了,尽管它应该通过:

@Test
public void searchTest2() { 
    List<Adoption> adoptionList = this.service.search("",0);
    Assert.assertEquals(petFilteredAdoptions,adoptionList);
}

为了解决这个问题,我尝试模拟 isPresent() 方法:

Mockito.when(this.petRepository.findById(0).isPresent()).thenReturn(true);

但我遇到以下异常:

org.mockito.exceptions.misusing.WrongTypeOfReturnValue: Boolean cannot be returned by findById() findById() should return Optional*** If you're unsure why you're getting above error read on. Due to the nature of the syntax above problem might occur because:

  1. This exception might occur in wrongly written multi-threaded tests. Please refer to Mockito FAQ on limitations of concurrency testing.
  2. A spy is stubbed using when(spy.foo()).then() syntax. It is safer to stub spies -
    • with doReturn|Throw() family of methods. More in javadocs for Mockito.spy() method.

我还尝试了以下变体:

Mockito.doReturn(true).when(this.petRepository.findById(0)).isPresent();

But then I got the following exception:

org.mockito.exceptions.misusing.UnfinishedStubbingException: Unfinished stubbing detected here: -> at mk.finki.ukim.milenichinja.ServiceTests.AdoptionServiceFilterTests.init(AdoptionServiceFilterTests.java:87)

E.g. thenReturn() may be missing. Examples of correct stubbing: when(mock.isOk()).thenReturn(true); when(mock.isOk()).thenThrow(exception); doThrow(exception).when(mock).someVoidMethod(); Hints:

  1. missing thenReturn()
  2. you are trying to stub a final method, which is not supported
  3. you are stubbing the behaviour of another mock inside before 'thenReturn' instruction is completed

有什么解决这个问题的想法吗?

init 方法中,您将模拟实例 this.petRepository 上的 findById 存根到 return 一个 non-mock 可选,这很好。在您的新测试中,您试图为 isPresent 设置一个 return 值,您不能这样做,因为 Optional 不是模拟。如果你想覆盖 per-test 行为,你需要将 findById 存根到 return 一个不同实例的 Optional。因此,这是正确的,尽管它看起来与 init 中的完全一样,因此它无法告诉您测试失败的原因。

Mockito.when(this.petRepository.findById(0))
    .thenReturn(java.util.Optional.of(pet));

Mockito 通过创建一个 mock 对象 来工作,subclass 是一个 class 并覆盖每个方法 .覆盖的方法是与静态 (ThreadLocal) 基础结构交互的方法,允许您使用 when 语法。这里重要的是 when 忽略了它的参数,而是试图模拟 你用模拟 进行的最后一次交互。您可以在 SO 问题 How does mockito when() invocation work? and How do Mockito matchers work?.

中找到更多信息

当您看到这个电话时:

Mockito.when(this.petRepository.findById(0))
    .thenReturn(java.util.Optional.of(pet));

那么它会如您所愿地工作:

  1. petRepository 是一个模拟,findById 大概是一个可重写的方法,Mockito 记录了你用参数 0.
  2. 调用它的事实
  3. findById 还没有任何行为存根,所以它执行默认行为,returning null.
  4. when 不关心它刚刚收到 null,因为 null 没有告诉它任何关于调用什么方法来获取 null .相反,它会回顾其最近的记录 (findById(0)),并且 return 是一个具有您期望的 thenVerb 方法的对象。
  5. 你调用 thenReturn,所以 Mockito 设置 petRepository 到 return 你创建并传入的 Optional 实例。

但是当你尝试这个调用时:

Mockito.when(this.petRepository.findById(0).isPresent()).thenReturn(true);

那么最近的交互不是 isPresent,而是 findById,因此 Mockito 假设您想要 findById(0)thenReturn(true) 并抛出 WrongTypeOfReturnValue。 Optional 不是模拟,因此与它交互不会让 Mockito 记录它的交互或重放您的行为。对于它的价值,我也不建议模拟它:Optional is a final class, and though Mockito has recently added some support for mocking final types,Optional 足够简单明了,因此只 return 你想要的 Optional 实例比试图模拟它更有意义。

综上所述,您的代码看起来是正确的;只要 PetRepository 是一个接口,我就看不到任何关于您的方法的外观或您的模拟的外观会导致 this.petRepository.findById(0) 到 return 缺席可选的方式。事实上,我什至没有看到你会在哪里 创建 一个缺席的 Optional for it to return,所以我只能猜测你在测试中使用了更多真实的对象比你想象的要好。