首页 > 解决方案 > 如何计算使用 Junit 和 Mockito 调用方法的次数?

问题描述

我想在 ProductService 中测试这个方法:

@Override
    public void validateUpdate(Product product, Product modifiedProduct, List<FieldError> errors) throws AppException {
        if(modifiedProduct == null || product == null) {
            addError(errors, "product", "Product cannot be null");
        }else {
            validateName(modifiedProduct.getName(), errors);
            validateShortDescription(modifiedProduct.getShortDescription(), errors);
            validateDescription(modifiedProduct.getDescription(), errors);
            validateRegularPriceAndPromotionprice(modifiedProduct.getRegularPrice(), modifiedProduct.getPromotionPrice(), errors);
            validateCategory(product.getCategory(), errors);
            validateCategoryMatches(product.getCategory(), modifiedProduct.getCategory(), errors);
            validateStore(product.getStore(), errors);
            validateSku(modifiedProduct.getSku(), errors);
            validateWeight(modifiedProduct.getWeight(), errors);
            validateQuantityInStock(modifiedProduct.getQuantityInStock(), errors);
            validateNotifyLowStock(modifiedProduct.getNotifyLowStock(), errors);
        }
    }

但我只想创建一个测试来验证是否正在调用所有方法。第一个是查看 addError 方法是否被调用一次:

@Test
    public void testValidateUpdateProduct() {
        ProductService productService = Mockito.mock(ProductService.class);
        List<FieldError> errors = new ArrayList<FieldError>();
        productService.validateUpdate(null, null, errors);
        Mockito.verify(productService, Mockito.times(1)).addError(errors, "product", "Product cannot be null");
    }

但后来我得到:

Wanted but not invoked:
productService.addError(
    [],
    "product",
    "Product cannot be null"
);
-> at ca.edooby.edoobyapi.service.ProductServiceTest.testValidateUpdateProduct2(ProductServiceTest.java:1022)

However, there was exactly 1 interaction with this mock:
productService.validateUpdate(
    ca.edooby.edoobyapi.model.Product@d2ca3a9,
    ca.edooby.edoobyapi.model.Product@2b26d289,
    []
);

标签: javajunitmockito

解决方案


您似乎正在尝试验证模拟对象的行为。这是一个坏主意有几个原因:

  1. 默认情况下,mockito 用存根替换对实际方法的所有调用(它什么都不做)
  2. 您的测试与类的内部实现高度耦合
  3. 所有被调用的较小的验证方法都必须放宽可见性级别(至少到包私有)才能工作
  4. 它表明该类具有多种职责-理想情况下,您将拥有许多较小的验证器,然后分别对其进行测试,然后您可以对组成的验证器进行单元测试-通过您喜欢的任何方法(然后您可以完全删除子-验证器)。

不过,您尝试做的是可行的。我称之为半模拟方法,如上所述 - 这是一种反模式。

为简单起见,假设我们有一个这样的类:

public class Baz {
    public void foo() {
        bar();
    }

    public void bar() {
       // stuff
    }
}

foo等效于您的validateUpdate方法,并且bar等效于从那里调用的几乎所有其他验证器方法。

测试将从配置半模拟开始:

    Baz baz = mock(Baz.class); 
    doCallRealMethod().when(baz).foo();

这解决了您的测试的核心问题 - 它实际上并没有调用原始validateUpdate方法,因为默认情况下 mockito 会存根所有方法(它们不会被调用)。

从那时起,测试照常进行:

    baz.foo();
    verify(baz).bar();

它通过了。再说一次,我不是教皇莫克修斯三世,测试者是某种权威,但总的来说,你应该:

  • 坚持检查代码的输入和输出(它不会将你与实现联系起来)
  • 如果您必须模拟/存根东西,请避免对被测系统这样做 - 这表明设计存在问题,并且可以进一步分解该类

希望有帮助。


推荐阅读