首页 > 解决方案 > JUnit - 如何以不同的参数以相同的方法测试重复指令?

问题描述

我有一个更新方法,但我在进行单元测试时遇到了麻烦。在该方法中,我需要验证要更改的对象是否存在于数据库中,并且我还需要检查用户插入的新数据是否会导致记录重复。为此,我在两个不同的时间使用 a 在数据库中进行搜索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;
    }

}

标签: javajunitjava-8mockitojava-11

解决方案


使用givenor定义模拟行为时when,您需要提供与被测试代码将提供的参数相匹配的参数。Mockito文档解释说:“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,但始终建议在覆盖hashCode时覆盖equals,以确保相等的对象也具有相等的哈希码。

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


推荐阅读