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

4

8 回答 8

27

Moq 不能模拟类上的非虚拟方法。要么使用其他模拟框架,例如Type mock Isolator,它实际上将 IL 编织到你的程序集中,要么在上面放置一个接口EmailService并模拟它。

于 2012-07-31T10:33:47.010 回答
8

模拟非虚拟方法涉及使用低级分析器 API。目前我认为唯一可用的选择是:

两者都是商业的,即使 JustMock 有一个精简版,模拟非虚拟方法也只能在商业版本中使用。正如评论中所指出的,微软的研究中有一些东西,在Pex 和 Moles项目中

于 2012-07-31T10:37:41.120 回答
7

2020 年 7 月更新: pose已弃用,目前推荐为: https ://github.com/riezebosch/Unmockable 感谢@Customizer 指出。

我还查看了https://github.com/Serg046/AutoFake,这在 @Serg046 看来是可行的——使用pose。允许您替换任何方法,包括静态或非虚拟方法。相当新的项目,但完全开源 MIT 许可证。 https://github.com/tonerdo/pose

于 2018-07-08T03:35:27.507 回答
6

必须使用虚拟方法进行模拟的替代方法是使用接口。这样你就可以模拟出整个依赖。

public interface IEmailService
{
    bool SendEmail();
    // etc...
}

public class EmailService : IEmailService
{
    //...
}

现在您可以创建接口IEmailService的模拟来模拟它的任何方法。当然,您必须将包含EmailService对象的变量类型更改为IEmailService适当的位置。

于 2012-07-31T10:35:50.110 回答
2

我很久以前就看到了这个问题,并意识到我想创建一些开源的东西来解决这个问题。所以它准备好了 - AutoFake。最令人兴奋的是它不需要任何疯狂的 CLR Profiler API。它只是一个普通的 .NET 包,仅此而已。Bellow 是您可以使用该库执行的操作的示例:

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);
}
于 2020-05-30T00:52:01.873 回答
1

正如@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);
    }

如您所见,我创建的测试与您尝试创建的测试相似。

于 2016-11-03T16:29:37.333 回答
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();
    }
}
于 2018-03-23T15:25:43.353 回答
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);
}
于 2021-02-09T11:19:38.053 回答