Mockito WrongTypeOfReturnValue:findById()不能返回布尔值

2022-02-23 00:00:00 unit-testing boolean spring mockito java

我正在尝试使用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: findById()不能返回布尔值。findById()应返回 可选* 如果您不确定为什么会出现上述错误,请继续阅读。由于 出现上述语法问题的原因可能是:

  1. 此异常可能在错误编写的多线程测试中发生。有关并发的限制,请参阅Mockito常见问题 正在测试。
  2. 间谍使用WHEN(spy.foo()).Then()语法进行存根。更安全的做法是铲除间谍-
    • 使用doReturn|Throw()方法系列。有关Mockito.spy()方法的javadoc的更多信息。

我还尝试了以下变体:

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

但随后我收到以下异常:

org.mockito.exceptions.misusing.UnfinishedStubbingException: 此处检测到未完成的存根: --mk.finki.ukim.milenichinja.ServiceTests.AdoptionServiceFilterTests.init(AdoptionServiceFilterTests.java:87)

的>;

例如,可能缺少TenReturn()。正确插桩的示例: When(mock.isOk()).thenReturn(True); When(mock.isOk()).thenThrow(异常); doThrow(exception).when(mock).someVoidMethod();提示:

  1. 缺少thenReturn()
  2. 您正在尝试存根Final方法,该方法不受支持
  3. 在"thenReturn"指令完成之前,您正在截断内部另一个模拟的行为

有什么想法可以解决此问题吗?


解决方案

init方法中,您正在清除模拟实例this.petRepository上的findById以返回非模拟可选,这很好。在您的新测试中,您试图为isPresent设置返回值,但您不能这样做,因为Optional不是模拟的。如果您想覆盖每个测试的行为,您将需要存根findById以返回不同实例的可选。因此,这是正确的,尽管它与init中显示的完全相同,因此它无法告诉您测试失败的原因。

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

Mockito的工作方式是创建模拟对象,该对象子类化一个类并覆盖每个方法。被覆盖的方法是与静电(线程本地)基础设施交互的方法,允许您使用when语法。这里重要的是when忽略了它的参数,而是尝试模拟您用模拟进行的最后一次交互。您可以在SO问题How does mockito when() invocation work?和How do Mockito matchers work?中找到更多信息。

当您看到此调用时:

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

然后它会按您的预期工作:

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

但当您尝试此呼叫时:

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

那么最近的交互不是isPresent,而是findById,所以Mockito假定您想要findById(0)thenReturn(true),并抛出WrongTypeOfReturnValue。Optional不是模拟的,所以与它交互不会让Mockito记录它的交互或重播您的行为。不管它有什么价值,我也不建议模仿它:Optional is a final class,虽然Mockito has recently added some support for mocking final types,但Optional足够简单和直接,只返回您想要的可选实例比试图模仿它更有意义。

话虽如此,您的代码看起来是正确的;只要PetRepository是一个接口,我就看不到任何关于您的方法的外观或您的模拟的外观会导致this.petRepository.findById(0)返回缺少的可选的内容。事实上,我甚至不知道您会在什么地方创建为它返回的缺少的可选,所以我只能猜测您在测试中使用的真实对象比您想象的要多。

相关文章