C# 如何模拟非虚拟方法? [TestMethod] 公共void TestMethod1() { var mock=new mock(); mock.Setup(x=>x.sendmail())。返回(true); var cus=新客户(); var result=cus.AddCustomer(mock.Object); 断言(结果); } 公共类客户 { 公共bool AddCustomer(EmailService EmailService) { emailService.sendmail(); Debug.WriteLine(“新增客户”); 返回true; } } 公共类电子邮件服务 { 公共虚拟bool sendmail() { 抛出新异常(“发送电子邮件失败,因为bla bla bla”); } }
C# 如何模拟非虚拟方法? [TestMethod] 公共void TestMethod1() { var mock=new mock(); mock.Setup(x=>x.sendmail())。返回(true); var cus=新客户(); var result=cus.AddCustomer(mock.Object); 断言(结果); } 公共类客户 { 公共bool AddCustomer(EmailService EmailService) { emailService.sendmail(); Debug.WriteLine(“新增客户”); 返回true; } } 公共类电子邮件服务 { 公共虚拟bool sendmail() { 抛出新异常(“发送电子邮件失败,因为bla bla bla”); } },c#,.net,moq,C#,.net,Moq,EmailService.SendEmail方法必须是虚拟的才能模拟它。有没有办法模拟非虚拟方法?Moq无法模拟类上的非虚拟方法。或者使用其他模拟框架,例如将IL实际编织到程序集中的框架,或者在EmailService上放置一个接口并模拟该框架。必须使用虚拟方法进行模拟的替代方法是使用接口。这样,您就可以模拟出整个依赖关系 [TestMethod] public void TestMethod1() { var mock = new Mock<EmailService>();
EmailService.SendEmail
方法必须是虚拟的才能模拟它。有没有办法模拟非虚拟方法?Moq无法模拟类上的非虚拟方法。或者使用其他模拟框架,例如将IL实际编织到程序集中的框架,或者在EmailService
上放置一个接口并模拟该框架。必须使用虚拟方法进行模拟的替代方法是使用接口。这样,您就可以模拟出整个依赖关系
[TestMethod]
public void TestMethod1()
{
var mock = new Mock<EmailService>();
mock.Setup(x => x.SendEmail()).Returns(true);
var cus = new Customer();
var result = cus.AddCustomer(mock.Object);
Assert.IsTrue(result);
}
public class Customer
{
public bool AddCustomer(EmailService emailService)
{
emailService.SendEmail();
Debug.WriteLine("new customer added");
return true;
}
}
public class EmailService
{
public virtual bool SendEmail()
{
throw new Exception("send email failed cuz bla bla bla");
}
}
现在,您可以创建接口
IEmailService
的模拟,以便模拟其任何方法。当然,您必须在适当的情况下将包含EmailService
对象的变量类型更改为IEmailService
。模拟非虚拟方法涉及使用低级探查器API。目前,我认为唯一的选择是:
正如评论中所指出的,在项目中,正如@aqwert和@Felice在使用时所写的那样,可以(而且非常容易)模拟非虚拟方法,而无需添加或更改任何代码,例如:
public interface IEmailService
{
bool SendEmail();
// etc...
}
public class EmailService : IEmailService
{
//...
}
[测试方法,隔离]
公共void TestMethod1()
{
var mock=Isolate.Fake.Instance();
隔离.WhenCalled(()=>mock.sendmail()).WillReturn(true);
var cust=新客户();
var结果=客户添加客户(模拟);
断言(结果);
}
正如您所见,我创建的测试与您尝试创建的测试类似。模拟非虚拟方法的唯一方法是模拟用于使用非虚拟方法实现该类的接口。下面是一个例子
[TestMethod,Isolated]
public void TestMethod1()
{
var mock = Isolate.Fake.Instance<EmailService>();
Isolate.WhenCalled(() => mock.SendEmail()).WillReturn(true);
var cust = new Customer();
var result = cust.AddCustomer(mock);
Assert.IsTrue(result);
}
公共接口IEEmployee
{
DateTime GetDateofJoining(int-id);
}
公营雇员
{
public DateTime GetDateofJoining(int-id)
{
返回日期时间。现在;
}
}
公共课程
{
静态void Main(字符串[]参数)
{
var employee=new Mock();
Setup(x=>x.GetDateofJoining(It.IsAny()).Returns((int x)=>DateTime.Now);
Console.WriteLine(employee.Object.GetDateofJoining(1));
Console.ReadLine();
}
}
于2020年7月更新:
pose
已被放弃,当前建议:
感谢@Customizer的指点
我也在检查@Serg046中的哪个似乎可行
——
使用姿势
。允许您替换任何方法,包括静态或非虚拟方法。相当新的项目,但完全开放源代码MIT许可证。
我很久以前就看到了这个问题,并且意识到我想创建一些开源的东西来解决这个问题。所以它已经准备好了。最令人兴奋的是,它不需要任何疯狂的CLR分析器API。它只是一个普通的.NET包,仅此而已。下面是使用该库可以执行的操作的示例:
public interface IEmployee
{
DateTime GetDateofJoining(int id);
}
public class Employee
{
public DateTime GetDateofJoining(int id)
{
return DateTime.Now;
}
}
public class Program
{
static void Main(string[] args)
{
var employee = new Mock<IEmployee>();
employee.Setup(x => x.GetDateofJoining(It.IsAny<int>())).Returns((int x) => DateTime.Now);
Console.WriteLine(employee.Object.GetDateofJoining(1));
Console.ReadLine();
}
}
公共类日历
{
publicstaticdatetime昨天=>DateTime.Now.AddDays(-1);
内部任务AddSomeMinutesAsync(日期时间日期)=>Task.Run(()=>AddSomeMinutes(日期));
公共静态DateTime AddSomeMinutes(DateTime date)=>date.AddMinutes(new Random().Next(1,10));
}
[事实]
昨天公众假期
{
var fake=new fake();
var sut=fake.Rewrite(()=>Calendar.dayed);
sut.Replace(()=>DateTime.Now).Return(新的DateTime(2016,8,day:8));
Assert.Equal(新的日期时间(2016,8,7),sut.Execute());
}
[事实]
公共异步任务AddSomeMinutesAsync_\u MinutesAdded()
{
var值=7;
var date=新的日期时间(2016年8月8日,小时:0,分钟:0,秒:0);
var fake=new fake();
var sut=false.Rewrite(f=>f.AddSomeMinutesAsync(date));
sut.Replace((随机r)=>r.Next(1,10))//Arg.Is(i=>i==10)也是可能的
//r.Next(1,11)以“预期-11,实际-10”失败
.ExpectedCalls(1)//c=>c>1失败,返回“实际值-1”
.返回值(随机值);
Assert.Equal(date.AddMinutes(randomValue),wait sut.Execute());
}
[事实]
公共无效添加分钟\u某天\u事件记录()
{
var events=新列表();
var fake=new fake();
var sut=false.Rewrite(()=>Calendar.AddSomeMinutes(新日期时间(2016,8,8));
sut.Prepend(()=>events.Add(“第一行”);
sut.Prepend(()=>events.Add(“AddMinutes(…)调用之前的行”))
.Before((DateTime date)=>date.AddMinutes(Arg.IsAny());
sut.Append(()=>events.Add(“new Random()调用后的行”))
.After(()=>new Random());
sut.Append(()=>events.Add(“最后一行”);
sut.Execute();
Assert.Equal(新[]
{
“第一行”,
“new Random()调用后的行”,//实际上,此调用更早
“AddMinutes(…)呼叫前的线路”,
“最后一行”
},
事件);
}
作为一种变通方法,您可以不使用方法本身,而是创建虚拟包装器方法
public class Calendar
{
public static DateTime Yesterday => DateTime.Now.AddDays(-1);
internal Task<DateTime> AddSomeMinutesAsync(DateTime date) => Task.Run(() => AddSomeMinutes(date));
public static DateTime AddSomeMinutes(DateTime date) => date.AddMinutes(new Random().Next(1, 10));
}
[Fact]
public void Yesterday_SomeDay_ThePrevDay()
{
var fake = new Fake<Calendar>();
var sut = fake.Rewrite(() => Calendar.Yesterday);
sut.Replace(() => DateTime.Now).Return(new DateTime(2016, 8, day: 8));
Assert.Equal(new DateTime(2016, 8, 7), sut.Execute());
}
[Fact]
public async Task AddSomeMinutesAsync_SomeDay_MinutesAdded()
{
var randomValue = 7;
var date = new DateTime(2016, 8, 8, hour: 0, minute: 0, second: 0);
var fake = new Fake<Calendar>();
var sut = fake.Rewrite(f => f.AddSomeMinutesAsync(date));
sut.Replace((Random r) => r.Next(1, 10)) // Arg.Is<int>(i => i == 10) is also possible
// r.Next(1, 11) fails with "Expected - 11, actual - 10"
.ExpectedCalls(1) // c => c > 1 fails with "Actual value - 1"
.Return(randomValue);
Assert.Equal(date.AddMinutes(randomValue), await sut.Execute());
}
[Fact]
public void AddSomeMinutes_SomeDay_EventsRecorded()
{
var events = new List<string>();
var fake = new Fake<Calendar>();
var sut = fake.Rewrite(() => Calendar.AddSomeMinutes(new DateTime(2016, 8, 8)));
sut.Prepend(() => events.Add("The first line"));
sut.Prepend(() => events.Add("The line before AddMinutes(...) call"))
.Before((DateTime date) => date.AddMinutes(Arg.IsAny<int>()));
sut.Append(() => events.Add("The line after new Random() call"))
.After(() => new Random());
sut.Append(() => events.Add("The last line"));
sut.Execute();
Assert.Equal(new[]
{
"The first line",
"The line after new Random() call", // indeed, this call is earlier
"The line before AddMinutes(...) call",
"The last line"
},
events);
}
然后在测试类中重写它:
public class EmailService
{
protected virtual void SendEmailReal(){
throw new Exception("send email failed cuz bla bla bla");
}
public void bool SendEmail()
{
return SendEmailReal();
}
}
试验方法本身:
abstract class TestEmailService: EmailService{
public abstract override bool SendEmailReal();
}
[TestMethod]
公共void TestMethod1()
{
var mock=new mock();
mock.Setup(x=>x.sendmailreal())。返回(true);
var cus=新客户();
var result=cus.AddCustomer(mock.Object);
断言
[TestMethod]
public void TestMethod1()
{
var mock = new Mock<TestEmailService>();
mock.Setup(x => x.SendEmailReal()).Returns(true);
var cus = new Customer();
var result = cus.AddCustomer(mock.Object);
Assert.IsTrue(result);
}