【问题标题】:JUnit - How to test duplicate instruction in same method with different parameter?JUnit - 如何以不同的参数以相同的方法测试重复指令?
【发布时间】:2021-08-06 22:23:23
【问题描述】:

我有一个更新方法,但我在进行单元测试时遇到了问题。在该方法中,我需要验证要更改的对象是否存在于数据库中,并且我还需要检查用户插入的新数据是否会导致记录重复。为此,我在两个不同的时间使用findById() 在数据库中进行搜索:

    public void update(Form form, Long storeCode, Long productCode, Long purchaseQuantity) {

    var id = new PrimaryKeyBuilder(DiscountPK.builder().build()).build(storeCode,productCode, purchaseQuantity);
    var targetToUpdate = repository.findById(id).orElseThrow(NotFoundException::new);

    var dataToInsert = SerializationUtils.clone(id);
    dataToInsert.setPurchaseQuantity(form.getPurchaseQuantity());

    var newPk = repository.findById(dataToInsert);
    throwExceptionIf(newPk.isPresent(), new DuplicatedPkException());

    targetToUpdate.getId().setPurchaseQuantity(form.getPurchaseQuantity());
    targetToUpdate.setDiscountPercentage(form.getDiscountPercentage());

    repository.save(targetToUpdate);

}

问题是:我无法在单元测试中区分这两个findById() 指令。不是成功通过它,而是在我的第一次验证中抛出NotFoundException。就好像第一个 given() 语句被忽略,只考虑第二个语句

    @Test
    public void update_successfully() {

        var targetToUpdate = ObjectFactory.createMain();
        var form = FormFactory.createUpdateForm();
        var id = ObjectFactory.createFirstAux();
        var dataToInsert = ObjectFactory.createSecondAux();

        given(repository.findById(id)).willReturn(Optional.of(targetToUpdate));
        given(repository.findById(dataToInsert)).willReturn(Optional.empty());

        given(repository.save(any())).willReturn(targetToUpdate);

        service.update(form, STORE_CODE, PRODUCT_CODE, PURCHASE_QUANTITY);
        verify(repository).save(targetToUpdate);
    }

奖励代码:构建传入findById()的对象的类

 public class PrimaryKeyBuilder {

    private final DiscountPK id;

    public PrimaryKeyBuilder(DiscountPK id) {
        this.id = id;
    }

    public DiscountPK build(Long storeCode, Long productCode, Long purchaseQuantity) {

        id.setPurchaseBoxQuantity(purchaseBoxQuantity);
        id.setProduct(Product.builder().id(ProductPK
                .builder().productCode(productCode).store(Store.builder().code(storeCode).build()).build()).build());

        return id;
    }

}

【问题讨论】:

  • 当您更改acceleratorRepository.findById(id) 部分以匹配任何参数时是否返回Optional.of(accelerator)?我怀疑匹配器不认为这个 id 对象与您在 update 函数中传递的任何内容相同。
  • 你好。术语“加速器”是在此处转录代码的错误。我调整了。关于您的假设,我还认为在我的测试中传递的 id 可能被认为与 Service 类执行的不同,即使值相同但对象本身不同(在服务中,id 是用新的)
  • DiscountPK 类是否定义了equalshashCode 方法?
  • 你好,@蒂姆·摩尔。 DiscountPK中没有equals/hashcode
  • 要让 Mockito 检测到两个不同的实例匹配,您需要定义 equalshashCode

标签: java junit java-8 mockito java-11


【解决方案1】:

使用givenwhen 定义模拟行为时,您需要提供与被测试代码将提供的参数相匹配的参数。 Mockito documentation 解释说:“Mockito 以自然 java 风格验证参数值:通过使用 equals() 方法。”

如果DiscountPK 类没有覆盖equals 方法,则将使用java.lang.Object 中的默认实现。这会比较对象身份,并且仅当两个对象都是同一个实例时才返回 true

如果您可以修改DiscountPK 类,最直接的解决方案是覆盖该类中的equalshashCode 方法,以根据其包含的值定义相等性。假设DiscountPK 类仅包含两个字段,分别名为purchaseBoxQuantityproduct,您可以使用java.util.Objects 类来定义这些字段:

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        DiscountPK that = (DiscountPK) o;
        return Objects.equals(purchaseBoxQuantity, that.purchaseBoxQuantity) && product.equals(that.product);
    }

    @Override
    public int hashCode() {
        return Objects.hash(purchaseBoxQuantity, product);
    }

这还假设Product 类定义了equalshashCode

请注意,尽管 Mockito 仅使用 equals 方法,而不是 hashCode,但始终建议在覆盖 equals 时覆盖 hashCode,以确保相等的对象也具有相等的哈希码。

我还建议使主键类不可变。这使得它们更容易推理,因为实体主键的内容永远不会改变。它还确保hashCode 的返回值在对象的生命周期内不会发生变化,这可能会导致错误。

【讨论】:

    【解决方案2】:

    如果您在模拟存储库,那么在使用 myRepo.save(myObj); 时它们实际上不会保存更改

    不过,您可以使用 Mockito 来模拟它:when(X).thenReturn(Y)

    例如,

    import org.junit.jupiter.api.Test;
    import org.junit.jupiter.api.extension.ExtendWith;
    
    import org.mockito.InjectMocks;
    import org.mockito.Mock;
    import org.mockito.junit.jupiter.MockitoExtension;
    
    @ExtendWith(MockitoExtension.class)
    class MyServiceTest {
        
        @Mock private MyRepo myRepo;
        @InjectMocks private MyService myService;
    
        ...
    
        @Test
        void myTest() {
            MyClass myObj = new MyClass();
            Mockito.when(myRepo.findById(myObj.getId())
              .thenReturn(Optional.of(myObj));
            // perform desired test
        }
    }
    

    【讨论】:

    • 你好。我将 given()...willReturn() 更改为使用 when()...thenReturn() 但错误仍在继续
    • 您是刚刚将 given()...willReturn() 更改为 when()...thenReturn() 还是您添加了导入并将 Mockito 预先添加到 when() 中?必须是Mockito.when(X).thenReturn(Y)
    • 我为 Mockito 添加了静态导入但没有任何改变,错误与使用 given() 相同... willReturn()
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-23
    • 1970-01-01
    • 2011-08-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多