Search code examples
unit-testingspock

Spock Mock not returning any data


I have a class which adds features to categories of products in the same category and prices based on the productCode of the product. I have a List of List of products in the same category and productCode for which the same price and category should be added. However, when I create a mock for the ProductDecorator it fails to return any products. I am using Lombok's @EqualsAndHashCode.Include on productCode in both classes

Here is the code

public class ProductDetail {
  String productCode;
  String category;
  Double price;

  public String getProductCode() {
    return productCode;
  }
}
class Product {
  String name;
  String productCode;
  String category;
  Double price;

  String getProductCode() {
    return productCode;
  }

  public Product addDetail(String category, Double price) {
    this.category = category;
    this.price = price;
    return this;
  }
}
public class ProductDecorator {
  List<Product> addProductFeature(List<Product> products, List<ProductDetail> details) {
    List<Product> productList = new ArrayList<>();
    products.forEach(product -> {
      details.forEach(detail -> {
        if (product.getProductCode().equals(detail.getProductCode())) {
          Product p = product.addDetail(detail.category, detail.price);
          productList.add(p);
        }
      });
    });
    return productList;
  }
}
  // 3 Products and 3 product details objects that match
  List<List<Product>> products = getProducts()
  List<ProductDetail> productDetailList = getProductDetails()

  ProductDecorator productDecorator = Mock()
  // Returns null all the time for all, just showing the first one
  productDecorator.addProductFeature(products[0],productDetailList)>>[products[0]]  

How do I successfully mock the decorator to match. I think it is not matching because of some equality issues.I tried using underscores but that did not work either. I'd be grateful for a solution to thsi problem


Solution

  • For me, it works like this, so probably your original code looks different:

    package de.scrum_master.stackoverflow.q77412076;
    
    import lombok.EqualsAndHashCode;
    import lombok.ToString;
    
    @EqualsAndHashCode
    @ToString
    public class ProductDetail {
      String productCode;
      String category;
      Double price;
    
      String getProductCode() {
        return productCode;
      }
    }
    
    package de.scrum_master.stackoverflow.q77412076;
    
    import lombok.EqualsAndHashCode;
    import lombok.ToString;
    
    @EqualsAndHashCode
    @ToString
    public class Product {
      String name;
      String productCode;
      String category;
      Double price;
    
      String getProductCode() {
        return productCode;
      }
    
      Product addDetail(String category, Double price) {
        this.category = category;
        this.price = price;
        return this;
      }
    }
    
    package de.scrum_master.stackoverflow.q77412076;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class ProductDecorator {
      List<Product> addProductFeature(List<Product> products, List<ProductDetail> details) {
        List<Product> productList = new ArrayList<>();
        products.forEach(product -> {
          details.forEach(detail -> {
            if (product.getProductCode().equals(detail.getProductCode())) {
              Product p = product.addDetail(detail.category, detail.price);
              productList.add(p);
            }
          });
        });
        return productList;
      }
    }
    
    package de.scrum_master.stackoverflow.q77412076
    
    import spock.lang.Specification
    
    class ProductDetailTest extends Specification {
      def 'test without mock'() {
        expect:
        new ProductDecorator().addProductFeature(products[0], productDetails) == [
          new Product(name: 'One', productCode: 'XY-1', category: 'vegetables', price: 12.34),
          new Product(name: 'Two', productCode: 'AB-2', category: 'flowers', price: 23.45),
          new Product(name: 'Three', productCode: 'QR-3', category: 'vegetables', price: 34.56)
        ]
        new ProductDecorator().addProductFeature(products[1], productDetails) == [
          new Product(name: 'Four', productCode: 'XY-1', category: 'vegetables', price: 12.34),
          new Product(name: 'Five', productCode: 'AB-2', category: 'flowers', price: 23.45),
          new Product(name: 'Six', productCode: 'QR-3', category: 'vegetables', price: 34.56)
        ]
      }
    
      def 'test with mock'() {
        given:
        ProductDecorator productDecorator = Mock()
        productDecorator.addProductFeature(products[0], productDetails) >> products[0]
    
        expect:
        productDecorator.addProductFeature(products[0], productDetails) == products[0]
        productDecorator.addProductFeature(products[1], productDetails) == null
      }
    
      List<List<Product>> getProducts() {
        [
          [
            new Product(name: 'One', productCode: 'XY-1'),
            new Product(name: 'Two', productCode: 'AB-2'),
            new Product(name: 'Three', productCode: 'QR-3')
          ],
          [
            new Product(name: 'Four', productCode: 'XY-1'),
            new Product(name: 'Five', productCode: 'AB-2'),
            new Product(name: 'Six', productCode: 'QR-3')
          ]
        ]
      }
    
      List<ProductDetail> getProductDetails() {
        [
          new ProductDetail(productCode: 'XY-1', category: 'vegetables', price: 12.34),
          new ProductDetail(productCode: 'AB-2', category: 'flowers', price: 23.45),
          new ProductDetail(productCode: 'QR-3', category: 'vegetables', price: 34.56)
        ]
      }
    }
    

    Please note: The mock should return products[0] rather than [products[0]], because ProductDecorator.addProductFeature returns List<Product>, not List<List<Product>>. But that is not the root cause of your problem.


    Try it in the Groovy Web Console.

    There, I used the Groovy equivalent of your Lombok annotations for simplicity's sake. @Canonical is like a combination of @ToString, @EqualsAndHashCode and @TupleConstructor. While recreating your situation, I was also printing values, hence the wish to have nice toString() methods. I also converted your lambdas into Groovy closures. Lambdas are fine in Groovy 4, but closures also work in older Groovy versions.