[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");
}
}
EmailService.SendEmail
方法必须是虚拟的才能模拟它。有没有办法模拟非虚拟方法?
答案 0 :(得分:22)
Moq无法在类上模拟非虚方法。使用其他模拟框架,例如Type mock Isolator,它实际上将IL编织到您的程序集中,或者在EmailService
上放置一个接口并模拟它。
答案 1 :(得分:8)
模拟非虚拟方法涉及使用低级分析器API。目前我认为唯一可用的选择是:
两者都是商业版,即使JustMock有精简版,也只是商业版可以使用模拟非虚拟方法。 正如评论中所指出的,在项目Pex and Moles
中有一些来自Microsoft研究的内容答案 2 :(得分:6)
必须使用虚拟方法进行模拟的替代方法是使用接口。这样你就可以模拟出一个完整的依赖。
public interface IEmailService
{
bool SendEmail();
// etc...
}
public class EmailService : IEmailService
{
//...
}
现在,您可以创建接口IEmailService
的模拟,以便模拟其任何方法。当然,您必须在适当的时候将包含EmailService
个对象的变量类型更改为IEmailService
。
答案 3 :(得分:4)
使用pose
。允许您替换任何方法,包括静态或非虚拟方法。相当新的项目,但完全开源的MIT许可证。
https://github.com/tonerdo/pose
答案 4 :(得分:2)
正如@aqwert和@Felice在使用Typemock Isolator时写的那样,在不添加或更改任何代码的情况下模拟非虚拟方法是可能的(并且非常简单),例如:
[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);
}
正如您所看到的,我创建的测试类似于您尝试创建的测试。
答案 5 :(得分:2)
我很早以前就看到了这个问题,并意识到我想创建一些开源的软件来解决这个问题。一切准备就绪-AutoFake。最令人兴奋的是,它不需要任何疯狂的CLR Profiler API。这只是一个普通的.NET包,仅此而已。以下是使用该库可以执行的操作的示例:
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);
}
答案 6 :(得分:0)
模拟非虚方法的唯一方法是模拟用于使用非虚方法实现该类的接口。以下是示例。
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();
}
}
答案 7 :(得分:0)
作为一种解决方法,您可以不使用方法本身,而是创建虚拟包装器方法
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]
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);
}