hbmaam
hbmaam

Reputation: 423

How to mock non virtual methods?

[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?

Upvotes: 42

Views: 56193

Answers (8)

Stadub Dima
Stadub Dima

Reputation: 858

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

Upvotes: -1

Serg046
Serg046

Reputation: 1173

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

Upvotes: 3

James Reategui
James Reategui

Reputation: 1347

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

Upvotes: 8

Rakesh Raut
Rakesh Raut

Reputation: 193

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

Upvotes: 0

aqwert
aqwert

Reputation: 10789

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.

Upvotes: 28

Sam
Sam

Reputation: 180

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.

Upvotes: 1

Felice Pollano
Felice Pollano

Reputation: 33272

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

Upvotes: 9

Alexander R
Alexander R

Reputation: 2476

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.

Upvotes: 6

Related Questions