JUnit - 如何使用不同参数测试相同方法中的重复指令?
JUnit - How to test duplicate instruction in same method with different parameter?
我有一个更新方法,但我在进行单元测试时遇到了问题。在该方法中,我需要验证要更改的对象是否存在于数据库中,我还需要检查用户插入的新数据是否不会导致记录重复。为此,我在两个不同的时间使用 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);
}
奖励代码:Class 构建传入的对象 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;
}
}
如果您正在模拟存储库,那么在使用 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
或 when
定义模拟行为时,您需要提供与被测试代码将提供的参数相匹配的参数。 Mockito documentation 解释说:“Mockito 以自然 java 风格验证参数值:通过使用 equals()
方法。”
如果 DiscountPK
class 没有覆盖 equals
方法,将使用 java.lang.Object
中的默认实现。这比较对象身份,并且只有 returns true
当两个对象是同一个实例时。
如果您可以修改 DiscountPK
class,最直接的解决方案是覆盖此 class 中的 equals
和 hashCode
方法, 根据它包含的值定义相等性。假设 DiscountPK
class 仅包含两个字段,名为 purchaseBoxQuantity
和 product
,您可以使用 java.util.Objects
class 以这种方式定义它们:
@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
class 定义了 equals
和 hashCode
。
注意,虽然Mockito只使用了equals
方法,并没有使用hashCode
,但总是建议在覆盖equals
时覆盖hashCode
,以确保相等对象也有相同的哈希码。
我还建议使主键 classes 不可变。这使得它们更容易推理,因为实体主键的内容永远不应该改变。它还确保 hashCode
的 return 值不会在对象的生命周期内发生变化,这可能会导致错误。
我有一个更新方法,但我在进行单元测试时遇到了问题。在该方法中,我需要验证要更改的对象是否存在于数据库中,我还需要检查用户插入的新数据是否不会导致记录重复。为此,我在两个不同的时间使用 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);
}
奖励代码:Class 构建传入的对象 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;
}
}
如果您正在模拟存储库,那么在使用 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
或 when
定义模拟行为时,您需要提供与被测试代码将提供的参数相匹配的参数。 Mockito documentation 解释说:“Mockito 以自然 java 风格验证参数值:通过使用 equals()
方法。”
如果 DiscountPK
class 没有覆盖 equals
方法,将使用 java.lang.Object
中的默认实现。这比较对象身份,并且只有 returns true
当两个对象是同一个实例时。
如果您可以修改 DiscountPK
class,最直接的解决方案是覆盖此 class 中的 equals
和 hashCode
方法, 根据它包含的值定义相等性。假设 DiscountPK
class 仅包含两个字段,名为 purchaseBoxQuantity
和 product
,您可以使用 java.util.Objects
class 以这种方式定义它们:
@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
class 定义了 equals
和 hashCode
。
注意,虽然Mockito只使用了equals
方法,并没有使用hashCode
,但总是建议在覆盖equals
时覆盖hashCode
,以确保相等对象也有相同的哈希码。
我还建议使主键 classes 不可变。这使得它们更容易推理,因为实体主键的内容永远不应该改变。它还确保 hashCode
的 return 值不会在对象的生命周期内发生变化,这可能会导致错误。