Spock 中的预定义模拟响应
Predefined mock response in Spock
我是 Spock 的新手,这个问题涉及 Java Testing with Spock 第 178 页的示例。被测class是一个购物应用的Basketclass,被测class的方法是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) 以查看是否有足够的库存仓库来完成订单。仓库 class 是篮子 class 的合作者,在以下测试中被模拟
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: 块验证布尔值 readyToShip 是否为真,并且 inventory.isProducAvailable() 被调用了两次而 inventory.preload() 根本没有被调用。倒数第二行是检查模拟的行为并告诉它 return true 调用 isProductAvailable()。我不明白的是,如果我将模拟预定义响应移动到 and: 块,测试将失败,如下所示
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: 块。
When mocking and stubbing the same method call, they have to happen in the same interaction. In particular, the following Mockito-style splitting of stubbing and mocking into two separate statements will not work:
setup:
subscriber.receive("message1") >> "ok"
when:
publisher.send("message1")
then:
1 * subscriber.receive("message1")
As explained in Where to Declare Interactions, the receive call will first get matched against the interaction in the then: block. Since that interaction doesn’t specify a response, the default value for the method’s return type (null in this case) will be returned. (This is just another facet of Spock’s lenient approach to mocking.). Hence, the interaction in the setup: block will never get a chance to match.
我是 Spock 的新手,这个问题涉及 Java Testing with Spock 第 178 页的示例。被测class是一个购物应用的Basketclass,被测class的方法是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) 以查看是否有足够的库存仓库来完成订单。仓库 class 是篮子 class 的合作者,在以下测试中被模拟
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: 块验证布尔值 readyToShip 是否为真,并且 inventory.isProducAvailable() 被调用了两次而 inventory.preload() 根本没有被调用。倒数第二行是检查模拟的行为并告诉它 return true 调用 isProductAvailable()。我不明白的是,如果我将模拟预定义响应移动到 and: 块,测试将失败,如下所示
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: 块。
When mocking and stubbing the same method call, they have to happen in the same interaction. In particular, the following Mockito-style splitting of stubbing and mocking into two separate statements will not work:
setup:
subscriber.receive("message1") >> "ok"
when:
publisher.send("message1")
then:
1 * subscriber.receive("message1")
As explained in Where to Declare Interactions, the receive call will first get matched against the interaction in the then: block. Since that interaction doesn’t specify a response, the default value for the method’s return type (null in this case) will be returned. (This is just another facet of Spock’s lenient approach to mocking.). Hence, the interaction in the setup: block will never get a chance to match.