-->

Using Moq to override virtual methods in the same

2020-02-10 01:01发布

问题:

We are using Moq to unit test our service classes, but are stuck on how to test situations where a service method calls another service method of the same class. I tried setting the method being called to virtual, but still couldn't figure out what to do then in Moq. For example:

public class RenewalService : IRenewalService
{
    //we've already tested this
    public virtual DateTime? GetNextRenewalDate(Guid clientId)
    {
        DateTime? nextRenewalDate = null;
        //...<snip> a ton of already tested stuff...

        return nextRenewalDate;
    }

    //but want to test this without needing to mock all 
    //the methods called in the GetNextRenewalDate method
    public bool IsLastRenewalOfYear(Renewal renewal)
    {
        DateTime? nextRenewalDate = GetNextRenewalDate(renewal.Client.Id);
        if (nextRenewalDate == null)
            throw new Exceptions.DataIntegrityException("No scheduled renewal date, cannot determine if last renewal of year");
        if (nextRenewalDate.Value.Year != renewal.RenewDate.Year)
            return true;
        return false;
    }
}

In the above example, our GetNextRenewalDate method is pretty complicated, and we've already unit tested it. However, we want to test the simpler IsLastRenewalOfYear without needing to mock everything needed for GetNextRenewalDate. Basically, we just want to mock GetNextRenewalDate.

I realize that I could create a new class that overrides GetNextRenewalDate and test the new class, but is there a way that I can leverage Moq to make this simpler?

回答1:

You can probably use partial mocking in this scenario, although all your methods would need to be virtual:

    var mock = new Moq.Mock<RenewalService>();
    mock.Setup(m => m.GetNextRenewalDate(It.IsAny<Guid>())).Returns(null);
    mock.CallBase = true;
    var results = mock.Object.IsLastRenewalOfYear(...);


回答2:

var mock = new Moq.Mock<RenewalService> { CallBase = true };
mock.Setup(m => m.GetNextRenewalDate(It.IsAny<Guid>())).Returns(null);
var results = mock.Object.IsLastRenewalOfYear(...);


回答3:

Edit: I now agree that this is not the right answer for Andrew's case. I want to leave this answer here for the comments thread. Please don't down vote any more :)

Before edit:

Generally mock object frameworks aren't designed to simplify the single class scenario, they're designed to isolate your code so you can test a single class.

If you try to use a mock object framework to solve this, the framework is just going to create a derived class and overload that method. The only thing different will be that you can do that in about 3 lines instead of 5, because you won't have to create the derived class definition.

If you'd like to use mock objects to isolate this behavior, then you should split up this class a bit. The GetNextRenewalDate logic could live outside the RenewalService object.

The fact that you're running into this problem may show that there is a simpler or more finely grained design yet to be discovered. Finding a class that is a bit less concrete, with a name like "manager" or "service" is often a hint that you could break up your design into smaller classes and get better reusability and maintainability out of it.