C# Moq您(可以吗?)如何向同一上下文添加两个不同(不兼容)的接口?
我的上下文是对我的数据模型的模拟 我在另一个名为“Email”的类中有一个方法“Send” 我的服务类使用模拟数据模型。 我的服务类“SendEmailForAlarm”中的一个方法访问模拟数据模型中的数据,然后调用Email类中的“Send”方法。 问题:我怎样才能让电子邮件类中的“Send”方法包含在我的模拟中 相关代码:C# Moq您(可以吗?)如何向同一上下文添加两个不同(不兼容)的接口?,c#,unit-testing,moq,C#,Unit Testing,Moq,我的上下文是对我的数据模型的模拟 我在另一个名为“Email”的类中有一个方法“Send” 我的服务类使用模拟数据模型。 我的服务类“SendEmailForAlarm”中的一个方法访问模拟数据模型中的数据,然后调用Email类中的“Send”方法。 问题:我怎样才能让电子邮件类中的“Send”方法包含在我的模拟中 相关代码: //INTERFACES public interface IEmail { ... void Send(string from, string to, stri
//INTERFACES
public interface IEmail
{
...
void Send(string from, string to, string subject, string body, bool isHtml = false);
}
public interface IEntityModel : IDisposable
{
...
DbSet<Alarm> Alarms { get; set; } // Alarms
}
//CLASSES
public class Email : IEmail
{
...
public void Send(string from, string to, string subject, string body, bool isHtml = false)
{
...sends the email
}
}
public partial class EntityModel : DbContext, IEntityModel
{
...
public virtual DbSet<Alarm> Alarms { get; set; } // Alarms
}
public class ExampleService : ExampleServiceBase
{
//Constructor
public ExampleService(IEntityModel model) : base(model) { }
...
public void SendEmailForAlarm(string email, Alarm alarm)
{
...
new Email().Send(from, to, subject, body, true);
}
}
//HELPER method
public static Mock<DbSet<T>> GetMockQueryable<T>(Mock<DbSet<T>> mockSet, IQueryable<T> mockedList) where T : BaseEntity
{
mockSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(mockedList.Expression);
mockSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(mockedList.ElementType);
mockSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(mockedList.GetEnumerator());
mockSet.Setup(m => m.Include(It.IsAny<string>())).Returns(mockSet.Object);
// for async operations
mockSet.As<IDbAsyncEnumerable<T>>()
.Setup(m => m.GetAsyncEnumerator())
.Returns(new TestDbAsyncEnumerator<T>(mockedList.GetEnumerator()));
mockSet.As<IQueryable<T>>()
.Setup(m => m.Provider)
.Returns(new TestDbAsyncQueryProvider<T>(mockedList.Provider));
return mockSet;
}
//接口
公共接口电子邮件
{
...
void Send(string from、string to、string subject、string body、bool isHtml=false);
}
公共接口IEntityModel:IDisposable
{
...
DbSet报警{get;set;}//报警
}
//班级
公共类电子邮件:IEmail
{
...
public void Send(string from、string to、string subject、string body、bool isHtml=false)
{
…发送电子邮件
}
}
公共部分类EntityModel:DbContext,IEntityModel
{
...
公共虚拟数据库集报警{get;set;}//报警
}
公共类ExampleService:ExampleServiceBase
{
//建造师
公共示例服务(IEntityModel模型):基(模型){}
...
公共无效SendEmailForAlarm(字符串电子邮件、警报)
{
...
新建电子邮件()。发送(发件人、收件人、主题、正文、true);
}
}
//辅助方法
公共静态Mock GetMockQueryable(Mock mockSet,IQueryable mockedList),其中T:BaseEntity
{
mockSet.As().Setup(m=>m.Expression).Returns(mockedList.Expression);
mockSet.As().Setup(m=>m.ElementType).Returns(mockedList.ElementType);
mockSet.As().Setup(m=>m.GetEnumerator()).Returns(mockedList.GetEnumerator());
Setup(m=>m.Include(It.IsAny()).Returns(mockSet.Object);
//用于异步操作
mockSet.As()
.Setup(m=>m.GetAsyncEnumerator())
.Returns(新的TestDbAsyncEnumerator(mockedList.GetEnumerator());
mockSet.As()
.Setup(m=>m.Provider)
.Returns(新的TestDbAsyncQueryProvider(mockedList.Provider));
返回模拟集;
}
以下单元测试通过,但不是为了验证电子邮件是否实际发送(或调用)
service.SendEmailForAlarm方法查询EntityModel.Alarms数据库集,并从检索到的对象中提取信息。然后调用Email类中的Send方法并返回void
[TestMethod]
public void CurrentTest()
{
//Data
var alarms = new List<Alarm> {CreateAlarmObject()} //create a list of alarm objects
//Arrange
var mockContext = new Mock<IEntityModel>();
var mockSetAlarm = new Mock<DbSet<Alarm>>();
var service = new ExampleService(mockContext.Object);
mockSetAlarm = GetMockQueryable(mockSetAlarm, alarms.AsQueryable());
mockContext.Setup(a => a.Alarms).Returns(mockSetAlarm.Object);
//Action
service.SendEmailForAlarm("test@domain.com", alarms[0]);
//NOTHING IS ASSERTED, SendEmailForAlarm is void so nothing returned.
}
[TestMethod]
公共测试()
{
//资料
var alarms=new List{CreateAlarmObject()}//创建报警对象列表
//安排
var mockContext=new Mock();
var mockSetAlarm=new Mock();
var service=newexampleservice(mockContext.Object);
mockSetAlarm=GetMockQueryable(mockSetAlarm,alarms.AsQueryable());
mockContext.Setup(a=>a.Alarms).Returns(mockSetAlarm.Object);
//行动
服务。SendEmailForAlarm(“test@domain.com“,报警[0]);
//未声明任何内容,SendEmailForAlarm无效,因此未返回任何内容。
}
我想做的是在Email.Send方法上放置一个拦截器,这样我就可以计算它执行了多少次,或者让mock-verify-validate调用了n次(在本例中是一次)
[TestMethod]
公共无效WhatIWantTest()
{
//资料
var alarms=new List{CreateAlarmObject()}//创建报警对象列表
var=0;
//安排
var mockEmail=new Mock();//新建
var mockContext=new Mock();
var mockSetAlarm=new Mock();
var service=newexampleservice(mockContext.Object);
mockEmail.Setup(u=>
u、 发送(It.IsAny(),It.IsAny(),It.IsAny(),
It.IsAny(),It.IsAny())
.Callback(()=>calls++);//新建在Email.Send方法上添加回拨
mockSetAlarm=GetMockQueryable(mockSetAlarm,alarms.AsQueryable());
mockContext.Setup(a=>a.Alarms).Returns(mockSetAlarm.Object);
//行动
服务。SendEmailForAlarm(“test@domain.com“,报警[0]);
//断言新的
Assert.AreEqual(1,调用);//检查回调计数以验证是否执行了发送
//或
//去掉上面的方法拦截器行,只验证模拟对象。
mockEmail.Verify(m=>m.Send(It.IsAny(),It.IsAny(),
It.IsAny(),It.IsAny(),
It.IsAny()),Times.Once();
}
当我运行此命令时,断言失败,因为“Calls”=0和Times。one也是零。
我认为我的问题是因为“服务”是使用访问报警数据所需的“mockContext”(IEntityModel)创建的,但Send方法不是mockContext的一部分。如果我模拟“mockEmail”并添加回叫,我就不知道如何将其添加到mockContext中
如何将Mock IEmail和Mock IEntityModel添加到同一上下文中,或者我的做法完全错了吗?只是澄清一下:您正在创建一个Mock,但从我所看到的情况来看,没有任何东西可以告诉代码使用此Mock进行任何操作。您不需要在任何地方使用该模拟,只需创建它,然后稍后测试它;难怪没有人叫它 我认为您的问题在于您的
ExampleService
正在实例化一个新的Email()
类:
new Email().Send(from, to, subject, body, true);
而更好的方法是在构造函数中提供IEmail
:
IEmail _email;
//Constructor
public ExampleService(IEntityModel model, IEmail email) : base(model)
{
_email = email;
}
然后稍后使用此选项:
public void SendEmailForAlarm(string email, Alarm alarm)
{
_email.Send(from, to, subject, body, true);
}
然后,您可以将您的Mock
注入到ExampleService
中,并检查是否适当地调用了它
如果很难/不可能为构造函数注入正确设置DI,那么您可以拥有一个公共属性,它允许您将类中的IEmail替换为另一个用于单元测试的IEmail,但这有点混乱:
public class ExampleService
{
private IEmail _email = null;
public IEmail Emailer
{
get
{
//if this is the first access of the email, then create a new one...
if (_email == null)
{
_email = new Email();
}
return _email;
}
set
{
_email = value;
}
}
...
public void SendEmailForAlarm(string email, Alarm alarm)
{
//this will either use a new Email() or use a passed-in IEmail
//if the property was set prior to this call....
Emailer.Send(from, to, subject, body, true);
}
}
然后在你的测试中:
//Arrange
var mockEmail = new Mock<IEmail>(); //NEW
var mockContext = new Mock<IEntityModel>();
var mockSetAlarm = new Mock<DbSet<Alarm>>();
var service = new ExampleService(mockContext.Object);
//we set the Emailer object to be used so it doesn't create one..
service.Emailer = mockEmail.Object;
//排列
var mockEmail=new Mock()//新的
var mockContext=new Mock();
var mockSetAlarm=new Mock();
var service=newexampleservice(mockContext.Object);
//我们设置要使用的Emailer对象,这样它就不会创建一个。。
service.Emailer=mockEmail.Object;
如果有一个public //Arrange
var mockEmail = new Mock<IEmail>(); //NEW
var mockContext = new Mock<IEntityModel>();
var mockSetAlarm = new Mock<DbSet<Alarm>>();
var service = new ExampleService(mockContext.Object);
//we set the Emailer object to be used so it doesn't create one..
service.Emailer = mockEmail.Object;