41
[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");
    }
}

The EmailService.SendEmail method must be virtual to mock it. Is there any way to mock non virtual methods?

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
hbmaam
  • 413
  • 1
  • 4
  • 4
  • Possible duplicate of [Mocking non-virtual methods in C#](https://stackoverflow.com/questions/1073684/mocking-non-virtual-methods-in-c-sharp) – phuclv Oct 01 '19 at 11:07

8 Answers8

28

Moq cannot mock non virtual methods on classes. Either use other mocking frameworks such as Type mock Isolator which actually weaves IL into your assembly or place an interface on EmailService and mock that.

Felice Pollano
  • 32,832
  • 9
  • 75
  • 115
aqwert
  • 10,559
  • 2
  • 41
  • 61
  • I know this a very old post, but had some similar issue. Are there any down sides of converting the method into virtual, when the actual application runs? – CrazyCoder Jun 26 '18 at 05:24
  • 1
    I recommend asking a separate question in SO. – aqwert Jun 26 '18 at 22:34
9

Mocking non virtual methods involves the use of low level profiler API. At the moment I think the only options available are :

both are commercial, even if JustMock have a lite edition, mocking non virtual methods are available just with the commercial version. As pointed in the comments there is something from Microsoft research, in the project Pex and Moles

Felice Pollano
  • 32,832
  • 9
  • 75
  • 115
8

Updated july 2020: pose Has been abandoned, current recommendation is: https://github.com/riezebosch/Unmockable Thanks to @Customizer for pointing out.

I was also checking out https://github.com/Serg046/AutoFake which seems viable from @Serg046 —— Use pose. Allows you to replace any method including static or non virtual. Fairly new project, but fully open source MIT license. https://github.com/tonerdo/pose

James Reategui
  • 1,307
  • 2
  • 16
  • 23
  • Seems to be dead: https://github.com/tonerdo/pose/issues/51. The framework https://github.com/riezebosch/Unmockable is advertised as an alternative in this issue. – Customizer Jul 02 '20 at 20:33
6

The alternative to having to use virtual methods for mocking is to use interfaces. This way you can mock out a whole dependency.

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

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

Now you can create mocks of the interface IEmailService to let you mock any of its methods. Of course, you'll have to change the types of variables containing EmailService objects to IEmailService where appropriate.

Alexander R
  • 2,468
  • 24
  • 28
  • I think using this technique is good, and there are certainly advantages to using interfaces. But if you're evaluating between the two options for a non-virtual method to be mockable: 1) make it virtual, 2) use it as part of the interface, what is the advantage of 2 over 1? Even if you pick 2, then you're effectively making the method virtual since it's now part of an interface. – zumalifeguard Jun 25 '15 at 17:10
3

I saw this question much time ago and realized that I would want to create something opensourced to solve the issue. So it is ready - AutoFake. The most exciting thing is that it doesn't require any crazy CLR Profiler API. It is just a normal .NET package and that's it. Bellow is the example of what you can do using the library:

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);
}
Serg046
  • 1,043
  • 1
  • 13
  • 42
1

As @aqwert and @Felice wrote when using Typemock Isolator it's possible (and pretty easy) to mock Non-virtual methods without adding or changing any code, for example:

[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);
    }

as you can see the test i've created is similar to the test you tried to create.

Sam
  • 182
  • 6
0

The only way to mock non virtual methods is to mock interface used to implement that class with non virtual methods. Below is the example.

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();
    }
}
Rakesh Raut
  • 183
  • 3
  • 12
-1

As a workaround you can use not the method itself but create virtual wrapper method instead

public class EmailService
{     
    protected virtual void SendEmailReal(){
        throw  new Exception("send email failed cuz bla bla bla");
    }
        
    public void bool SendEmail()
    {
        return SendEmailReal();
    }
}

And then override it in the test class:

abstract class TestEmailService: EmailService{

     public abstract override  bool SendEmailReal();
}

The test methods itself:

[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);
}
Stadub Dima
  • 858
  • 10
  • 24
  • this answer did have 2 upwotes but now it has -1, similar things I see with other answers(not mine) over staskowerflow, staskowerflow is accupied by some upset bots? – Stadub Dima Feb 28 '23 at 07:08