Spock中的预定义模拟响应

时间:2017-07-27 20:49:08

标签: testing mocking spock

我是Spock的新手,这个问题是指使用Spock进行Java测试的第178页上的示例。被测试的类是购物应用程序的Basket类,正在测试的这个类的方法是canShipCompletely()

public class Basket {
  private WarehouseIneventory warehouseInventory;
  private ShippingCalculator shippingCalculator;
  protected Map<Product, Integer> contents = new HashMap<>();
  ...

  public void addProduct(Product product) {
    addProduct(product, 1);
  }

  public void addProduct(Product product, int times) {
    if (contents.containsKey(product)) {
      int existing = contents.get(product);
      contents.put(product, existing + times);
    } else {
      contents.put(product, times);
    }
  }

  public Boolean canshipCompletely() {
    if(warehouseInventory.isEmpty()) return false;

    try {
      for (Entry<Product, Integer> entry : contents.entrySet()) 
        boolean ok = warehouseInventory.isProductAvailable(
                                  entry.getKey().getName(), 
                                  entry.getValue()
                                  );
        if (!ok) {
          return false;
        }
    }
      return true;
    } catch (Exception e) {
      return false;
    }

  ...
}

此方法canShipCompletely()循环播放篮子中的项目(在Map内容中),对于每个项目,它调用warehouseInventory.isProductAvailable(product,count)以查看仓库中是否有足够的库存来填充命令。 Warehouse类是Basket类的协作者,它在以下测试中被模拟

def "Warehouse is queried for each product"() {
    given: "a basket, a TV and a camera"
    Product tv = new Product(name:"bravia",price:1200,weight:18)
    Product camera = new Product(name:"panasonic",price:350,weight:2)
    Basket basket = new Basket()

    and: "a warehouse with limitless stock"
    WarehouseInventory inventory = Mock(WarehouseInventory)
    basket.setWarehouseInventory(inventory)

    when: "user checks out two products"
    basket.addProduct tv
    basket.addProduct camera
    boolean readyToShip = basket.canShipCompletely()

    then: "order can be shipped"
    readyToShip
    2 * inventory.isProductAvailable( _ , _) >> true
    0 * inventory.preload(_ , _)
}

then:block验证布尔readyToShip是否为true,并且inventory.isProducAvailable()被调用了两次,并且根本没有调用inventory.preload()。倒数第二行是检查模拟的行为,并告诉它对isProductAvailable()的调用返回true。我不明白的是,如果我将模拟预定义响应移动到and:block,则测试将失败,如下所示

def "Warehouse is queried for each product"() {
    given: "a basket, a TV and a camera"
    Product tv = new Product(name:"bravia",price:1200,weight:18)
    Product camera = new Product(name:"panasonic",price:350,weight:2)
    Basket basket = new Basket()

    and: "a warehouse with limitless stock"
    WarehouseInventory inventory = Mock(WarehouseInventory)

    // ******** Move mock predefined response here  **********
    inventory.isProductAvailable( _ , _ ) >> true         
    basket.setWarehouseInventory(inventory)

    when: "user checks out two products"
    basket.addProduct tv
    basket.addProduct camera
    boolean readyToShip = basket.canShipCompletely()

    then: "order can be shipped"
    readyToShip
    2 * inventory.isProductAvailable( _ , _)
    0 * inventory.preload(_ , _)
}

我遇到的失败是对isProductAvailable()的调用太少:

调用次数太少:

2 * inventory.isProductAvailable(_,_)(1次调用)

不匹配的调用(按相似性排序):

1 * inventory.isEmpty()

我不明白为什么mock的预定义行为无法移动到and:block。

1 个答案:

答案 0 :(得分:0)

请参阅documentation

  

当模拟和存根相同的方法调用时,它们必须在相同的交互中发生。特别是,以下Mockito风格将存根和模拟分成两个单独的语句将不起作用:

setup:
subscriber.receive("message1") >> "ok"

when:
publisher.send("message1")

then:
1 * subscriber.receive("message1")
  

如在声明交互的位置中所解释的,接收调用将首先与then:块中的交互匹配。由于该交互未指定响应,因此将返回方法的返回类型的默认值(在本例中为null)。 (这只是Spock宽大的嘲弄方法的另一个方面。)因此,setup:block中的交互永远不会有机会匹配。