【问题标题】:Mockito WrongTypeOfReturnValue: Boolean cannot be returned by findById()Mockito WrongTypeOfReturnValue:findById() 不能返回布尔值
【发布时间】:2022-01-31 12:56:14
【问题描述】:

我正在尝试使用 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() 方法的 javadocs。

我还尝试了以下变体:

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

但后来我得到了以下异常:

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

例如thenReturn() 可能会丢失。正确的存根示例: 当(模拟。isOk())。然后返回(真); when(mock.isOk()).thenThrow(异常); doThrow(exception).when(mock).someVoidMethod();提示:

  1. 缺少 thenReturn()
  2. 您正在尝试存根不支持的最终方法
  3. 在“thenReturn”指令完成之前,您正在对内部另一个模拟的行为进行存根

任何想法如何解决这个问题?

【问题讨论】:

  • 感谢您如此清晰地显示您的代码。第二条错误消息中的第 87 行是哪一行?哪一行给出了 WrongTypeOfReturnValue 错误?
  • @JeffBowman 是这一行:Mockito.doReturn(true).when(this.petRepository.findById(0)).isPresent();

标签: java spring unit-testing mockito boolean


【解决方案1】:

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

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

Mockito 通过创建一个模拟对象来工作,该对象子类化一个类并覆盖每个方法。被覆盖的方法是与静态(ThreadLocal)基础设施交互的方法,允许您使用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 以返回您创建并传入的 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,尽管Mockito has recently added some support for mocking final types,Optional 足够简单明了,因此只返回您想要的 Optional 实例而不是试图模拟它更有意义。

综上所述,您的代码看起来不错;只要 PetRepository 是一个接口,我就看不到任何关于您的方法的外观或模拟的外观会导致this.petRepository.findById(0) 返回不存在的 Optional 的信息。事实上,我什至不知道你会在哪里创建一个缺席的 Optional 让它返回,所以我只能猜测你在测试中使用的真实对象比你想象的要多。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-04-12
    • 2013-08-18
    • 1970-01-01
    • 2019-07-08
    • 2020-08-05
    • 2011-12-14
    • 2020-07-29
    相关资源
    最近更新 更多