Reputation: 423
[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
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
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
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
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
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
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
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
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