首页 > 解决方案 > 有没有办法在服务中模拟(使用 Moq )服务并执行代码?

问题描述

我有一个使用 Moq 的测试问题。我到处看了,但我不太明白到底发生了什么。

我想测试一个名为 Basket 的类(服务),在该类中我作为 DI 传递另一个类(服务) DiscountService。

我面临的问题是,每当我模拟 Basket 类时,它都会很好地触发,但是当它到达在 DiscountService 中实际执行另一个方法的代码时,它只是跳过该方法而不是进入。我在做什么错误的?

我想可能是我没有正确地进行设置?

到目前为止,这是我的实际测试代码:

 [Test]
        public async System.Threading.Tasks.Task Test1Async()
        {
            //Arrange
            var basketServiceMock = new Mock<IBasket>();
            var discountServiceMock = new Mock<IDiscountService>();

            List<BasketProductModel> productsModel = 
                new List<BasketProductModel> 
                { 
                    new BasketProductModel()
                    {
                        ProductName = "Milk",
                        ProductPrice = 10,
                        Quantity = 3
                    }
                };
            BasketModel basket = new BasketModel { Products = productsModel };
            basketServiceMock.Setup(x => x.Baskett)
                .Returns(new BasketModel { Products = productsModel });

            var product = new BasketProductModel()
            {
                ProductName = "Milk",
                ProductPrice = 10,
                Quantity = 1
            };
            var product1 = new BasketProductModel()
            {
                ProductName = "Milk",
                ProductPrice = 10,
                Quantity = 1
            };
            var product2 = new BasketProductModel()
            {
                ProductName = "Milk",
                ProductPrice = 10,
                Quantity = 1
            };
            var product3 = new BasketProductModel()
            {
                ProductName = "Milk",
                ProductPrice = 10,
                Quantity = 1
            };

            discountServiceMock.Setup(x => x.ApplyDiscount(ref basket, product));
            var basketService = new Basket.Basket(discountServiceMock.Object);


            //Act
            await basketService.AddProductToBasket(product);
            await basketService.AddProductToBasket(product1);
            await basketService.AddProductToBasket(product2);
            await basketService.AddProductToBasket(product3);

            //Assert

        }

这是我的篮子课:

       public class Basket : IBasket
    {
        private BasketModel _basketProducts;
        private double _totalCost;
        public BasketModel Baskett { 
            get {
                if (_basketProducts == null)
                    throw new ArgumentNullException("xxxxxxxxxx");

                return _basketProducts;
            } 
        }

        public double TotalCost
        {
            get
            {
                if (_totalCost == null)
                {
                    throw new ArgumentNullException("xxxxxxxxxx");
                }

                return _totalCost;
            }
        }

        private readonly IDiscountService _discountService;

        public Basket(IDiscountService discountService)
        {
            _discountService = discountService;

            _basketProducts = new BasketModel();
            _basketProducts.Products = new List<BasketProductModel>();
        }

        public async Task AddProductToBasket(BasketProductModel product)
        {
            if (product == null)
                throw new ArgumentNullException();


            var obj = _basketProducts.Products.FirstOrDefault(x => x.ProductName == product.ProductName);
            if (obj == null)
                _basketProducts.Products.Add(product);
            else
            {
                obj.Quantity += product.Quantity;
            }
            _discountService.ApplyDiscount(ref _basketProducts, product); <---This part is being skipped, do I miss something in the SETUP part?!

        }


    }

这是我的未触发的 DiscountService 类(ApplyDiscount 方法)

       public class DiscountService : IDiscountService
    {

        public static readonly ReadOnlyCollection<string> discountedProducts = new List<String> {
                                                                                            "Milk",
                                                                                            "Butter",
                                                                                            "Bread",
        }.AsReadOnly();

        public void ApplyDiscount(ref BasketModel basket, BasketProductModel product)
        {
            if (discountedProducts.Contains(product.ProductName) && basket.Products.Count > 0)
                product.CalculateDiscount(basket);
        }

    }

这是 BasketProductModel(篮子里的产品)

    public class BasketProductModel : DiscountProcess
    {
        public BasketProductModel()
        {
            Init(this);
        }

        public string ProductName { get; set; }
        public double ProductPrice { get; set; }
        public int Quantity;
        public int Freebies { get; set; }
        public double Total { get; set; }
    }

这是上面类中使用的抽象类,用于使用一些预配置的实现


    public abstract class DiscountProcess
    {
        private BasketProductModel _obj { get; set; }
        public BasketProductModel Obj
        {
            get
            {    // check _obj is inited:
                if (_obj == null) throw new Exception();
                return _obj;
            }
        }

        protected void Init(BasketProductModel bPModel)
        {
            _obj = bPModel;
        }

        public void CalculateDiscount(BasketModel basket)
        {
            var productContained = NumberOfProductsInBasket(ref basket, _obj);
            var obj = basket.Products.FirstOrDefault(x => x.ProductName == _obj.ProductName);

            var freeItems = 0;
            switch (_obj.ProductName)
            {
                case "Milk":
                    if (productContained % 2 != 0)
                    {
                        obj.Freebies = numberOfFreeItems(obj.Quantity);
                        //AddQuantity(ref obj);
                    }
                    break;
                case "Butter":

                    break;
                case "Bread":
                    productContained = NumberOfProductsInBasket(ref basket, productName: "Butter");
                    if (productContained % 2 == 0)
                    {
                        CalculateDiscountedPrice(50, ref obj);
                    }
                    break;
                default:
                    break;
            }

            CalculateTotal(ref obj);
        }

        //...Other Methods...
    }

所以基本上我希望能够访问服务内的代码并执行所有代码,以便我可以断言篮子:)

关于如何实现这一点的任何想法?

最终目标:

我希望BaskettTo 拥有基于 DiscountService 对 ref 传递的 basked 所做的数据,以便我可以在最后断言。

标签: c#unit-testingintegration-testingmoq

解决方案


如果Basket是被测试的对象,那么它不应该被嘲笑。

//...

//Arrange
var basketServiceMock = new Mock<IBasket>(); //<-- Should not be mocked
var discountServiceMock = new Mock<IDiscountService>();

//...

相反,使用一个实际的实例,

var discountServiceMock = new Mock<IDiscountService>();
Basket basketServiceMock = new Basket(discountServiceMock .Object);

//...

并模拟完成测试用例所需的依赖项。

在这种情况下,主题会在您尝试在测试中设置的构造函数中初始化模型。由于模型正在由主题类初始化,因此无需尝试在测试中创建一个。只需根据测试需要填充它。

查看以下内容

[Test]
public async System.Threading.Tasks.Task Test1Async() {
    //Arrange
    
    var discountServiceMock = new Mock<IDiscountService>();

    Basket subject = new Basket(discountServiceMock.Object);

    List<BasketProductModel> productsModel = new List<BasketProductModel> { 
        new BasketProductModel() {
            ProductName = "Milk",
            ProductPrice = 10,
            Quantity = 3
        }
    };
    subject.Baskett.Products =  productsModel; 
    
    // Only matches if the ref argument to the invocation is the same instance
    discountServiceMock
        .Setup(x => x.ApplyDiscount(ref subject.Baskett, It.IsAny<BasketProductModel>()));
        //You need to decide what you want the mocked member to do.
    
    var product = new BasketProductModel() {
        ProductName = "Milk",
        ProductPrice = 10,
        Quantity = 1
    };
    var product1 = new BasketProductModel() {
        ProductName = "Milk",
        ProductPrice = 10,
        Quantity = 1
    };
    var product2 = new BasketProductModel() {
        ProductName = "Milk",
        ProductPrice = 10,
        Quantity = 1
    };
    var product3 = new BasketProductModel() {
        ProductName = "Milk",
        ProductPrice = 10,
        Quantity = 1
    };

    //Act
    await subject.AddProductToBasket(product);
    await subject.AddProductToBasket(product1);
    await subject.AddProductToBasket(product2);
    await subject.AddProductToBasket(product3);

    //Assert
    //...You need to decide what it is you are actually testing
    //and want to assert

}

观察:

被测成员被定义为async但实际上没有在该成员中等待。重新检查该成员的设计。


推荐阅读