有没有办法在服务中模拟(使用 Moq )服务并执行代码?
Is there a way to Mock (using Moq ) a service within a service and execute the code?
我有一个使用最小起订量的测试问题。我到处都看了,但我不太明白到底发生了什么。
我想测试一个叫做 Basket 的 Class(service),在里面 class 我作为 DI 传递了另一个 Class(service) DiscountService。
我面临的问题是,每当我模拟篮子 class 时,它都能很好地触发,但是当它到达实际执行 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
}
这是我的篮子class:
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 Class 未触发(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; }
}
这是上面class中使用的摘要class,用于使用一些预配置的实现
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 所做的操作获取数据,以便我可以在最后断言。
如果Basket
是被测对象,则不应被mock。
//...
//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);
//...
模拟完成测试用例所需的依赖关系。
本例中的主题在您尝试在测试中设置的构造函数中初始化了一个模型。由于模型正在由主题 class 初始化,因此无需在测试中尝试创建一个。只需根据测试需要填充即可。
查看以下内容
[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
,但该成员实际上没有等待任何内容。重新检查该成员的设计。
我有一个使用最小起订量的测试问题。我到处都看了,但我不太明白到底发生了什么。
我想测试一个叫做 Basket 的 Class(service),在里面 class 我作为 DI 传递了另一个 Class(service) DiscountService。
我面临的问题是,每当我模拟篮子 class 时,它都能很好地触发,但是当它到达实际执行 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
}
这是我的篮子class:
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 Class 未触发(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; }
}
这是上面class中使用的摘要class,用于使用一些预配置的实现
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 所做的操作获取数据,以便我可以在最后断言。
如果Basket
是被测对象,则不应被mock。
//...
//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);
//...
模拟完成测试用例所需的依赖关系。
本例中的主题在您尝试在测试中设置的构造函数中初始化了一个模型。由于模型正在由主题 class 初始化,因此无需在测试中尝试创建一个。只需根据测试需要填充即可。
查看以下内容
[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
,但该成员实际上没有等待任何内容。重新检查该成员的设计。