c# - 有没有办法在服务中模拟(使用 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...
}
所以基本上我希望能够访问服务内的代码并执行所有代码,以便我可以断言篮子:)
关于如何实现这一点的任何想法?
最终目标:
我希望Baskett
To 拥有基于 DiscountService 对 ref 传递的 basked 所做的数据,以便我可以在最后断言。
解决方案
如果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
但实际上没有在该成员中等待。重新检查该成员的设计。
推荐阅读
- c++ - 在我的主应用程序中加载一个 dll 怎么会导致 100 个 CPU 负载?
- firebase - 用于 Tensorflow.js 的 Keras 部署
- java - 来自静态嵌套类的代理
- javascript - React 列表框针对数千个选项进行了优化
- flutter - 如何在 Flutter 中启动/停止不活动计时器
- php - 递归 PHP - 在一个排列之后,为什么这个递归函数不返回一个值?
- apache-spark - Spark:使用特殊情况读取 dat 文件
- xml - 如何在应用模板中将字符串与 XSLT1.0 中的数组进行比较
- java - Java 将 md5 与来自另一个生成器的另一个 md5 进行比较:值不同
- html - MacOs sed 替换不适用于 html 文件