Groovy Spock测试中Mock ProductCategory的findAll方法失败
问题描述
我有一个用于关联Product与ProductCategory的服务类,依赖MongoDB的ProductCategoryRepository。该仓库自定义了一个接收productIds列表的findAll方法,用于批量查询对应的ProductCategory。但在Spock测试中Mock这个findAll方法时执行失败,抛出异常。
服务类代码
import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.ObjectUtils; import java.util.ArrayList; import java.util.List; @Service @Slf4j public class ProductsLinkService { private final ProductCategoryRepository productCategoryRepository; @Autowired public ProductsLinkService(ProductCategoryRepository productCategoryRepository) { this.productCategoryRepository = productCategoryRepository; } public List<Product> process(List<Product> products) { if (ObjectUtils.isEmpty(products)) { return new ArrayList<>(); } List<Long> productIds = MappingUtilityFunctions.retrieveIdsForDepartments(products); List<ProductCategory> productCategories = productCategoryRepository.findAll(productIds); return linkProductsWithCategory(products,productCategories); } private List<Product> linkProductsWithCategory(List<Product> products, List<ProductCategory> productCategories) { //... } }
Repository代码
import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.Query; import org.springframework.stereotype.Repository; import java.util.List; @Repository public interface ProductCategoryRepository extends MongoRepository<ProductCategory, Long> { @Query(value = "{ '_id' : {'$in' : ?0 } }") List<ProductCategory> findAll(List<Long> productIds); }
Spock测试代码
class ProductsLinkServiceSpec extends Specification { ProductCategoryRepository productCategoryRepository = Mock() List<Long> productIds = [11L, 22L, 33L] ProductsLinkService productsLinkService def setup(){ productsLinkService = new ProductsLinkService(productCategoryRepository) } def'Associating products with their product categories should be successful'(){ given: List<Product> products = .... List<ProductCategory> productCategories = ... // 此处抛出异常 productCategoryRepository.findAll(productIds) >> productCategories when: def productsWithCategories = productsLinkService.process(products) then: // 断言 } }
异常信息
groovy.lang.MissingMethodException: No signature of method: java.util.ArrayList.call() is applicable for argument types: () values: [] Possible solutions: tail(), tail(), wait(), any(), last(), tails() at com.adx.batch.linking.ProductsLinkServiceSpec.Associating products with their product categories should be successful(ProductsLinkServiceSpec.groovy:25)
原因与解决方案
问题根源
自定义的findAll(List<Long>)方法和MongoRepository自带的无参findAll()方法签名冲突,触发了Groovy的语法歧义:
Groovy允许对列表实例使用()语法(比如list(0)等价于list.get(0)),当你写productCategoryRepository.findAll(productIds)时,Groovy会误解析为:
- 调用无参的
productCategoryRepository.findAll(),得到一个List<ProductCategory>实例 - 尝试对这个列表调用
call(productIds),也就是用productIds作为索引取值,而ArrayList并没有call()方法,因此抛出异常。
解决方案
方案1:重命名自定义方法(推荐)
直接修改Repository中的方法名,避免和父接口方法冲突:
@Repository public interface ProductCategoryRepository extends MongoRepository<ProductCategory, Long> { @Query(value = "{ '_id' : {'$in' : ?0 } }") List<ProductCategory> findAllByProductIds(List<Long> productIds); }
同时更新服务类的调用代码:
List<ProductCategory> productCategories = productCategoryRepository.findAllByProductIds(productIds);
测试代码中的Mock语句也对应修改:
productCategoryRepository.findAllByProductIds(productIds) >> productCategories
方案2:明确指定方法参数类型
如果不想修改方法名,可以在Spock的Mock语句中通过类型约束消除歧义:
// 方式1:使用参数类型约束 productCategoryRepository.findAll(_ as List<Long>) >> productCategories // 方式2:强制转换参数类型 productCategoryRepository.findAll((List<Long>) productIds) >> productCategories
内容的提问来源于stack exchange,提问作者BreenDeen
相关产品推荐
相关产品推荐

