如何模拟非虚拟方法?

时间:2012-07-31 10:29:55

标签: c# .net moq

[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方法必须是虚拟的才能模拟它。有没有办法模拟非虚拟方法?

8 个答案:

答案 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);
}