jeudi 27 juillet 2017

Predefined mock response in Spock

I'm new to Spock and this question refers to the example on page 178 of Java Testing with Spock. The class under test is the Basket class for a shopping application, and the method of this class being tested is 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;
    }

  ...
}

This method canShipCompletely() loops over the items in the basket (in Map contents) and for each item, it makes a call to warehouseInventory.isProductAvailable(product, count) to see if there is sufficient stock in the warehouse to fill the order. The Warehouse class is a collaborator of the Basket class that is mocked in the following test

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(_ , _)
}

The then: block verifies the boolean readyToShip is true, and that inventory.isProducAvailable() was called twice and inventory.preload() was not called at all. The next to last line is both checking behavior of the mock and telling it to return true for calls to isProductAvailable(). What I don't understand is the test will fail if I move the mock predefined response to the and: block as follows

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(_ , _)
}

The failure I get is too few calls to isProductAvailable():

Too few invocations for:

2 * inventory.isProductAvailable( _ , _) (1 invocation)

Unmatched invocations (ordered by similarity):

1 * inventory.isEmpty()

I don't understand why the predefined behavior for the mock can't be moved to the and: block.

Aucun commentaire:

Enregistrer un commentaire