【发布时间】:2021-02-15 17:02:35
【问题描述】:
我有一个使用 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...
}
所以基本上我希望能够访问服务内的代码并执行所有代码,以便我可以断言篮子:)
知道如何实现这一点吗?
最终目标:
我希望Baskett 能够根据 DiscountService 对 ref 传递的 basked 所做的事情来获取数据,以便我可以在最后进行断言。
【问题讨论】:
-
为什么两个类要相互注入? (循环依赖)。哪一个被认为是被测对象?
-
@Nkosi 好点,我
ve spotted it as well...and Im 打算重构它,但是,被测对象应该是 basked,我们在里面注入了 discountService...(忽略里面的依赖DiscountService 是篮子) -
@Nkosi 我已经用一些新信息更新了这个问题 + 我重构了循环依赖:) 你介意随时看看吗?
-
你在嘲笑
DiscountService...为什么你会期望调用实际服务的方法? -
@IanKemp 好问题,这就是为什么我想我在这里,从我的错误中吸取教训。我想我不明白发生了什么,所以我需要一些帮助:)
标签: c# unit-testing integration-testing moq