简体   繁体   English

模拟静态方法选项

[英]Mocking static method options

I understand that you can't mock a static method with moq, but I was wondering what my possible options are 我知道你不能用moq模拟一个静态方法,但我想知道我的选择是什么

I have a controller class defined 我定义了一个控制器类

public class CustomerController : BaseController
{
    private ICustomerManager cm;

    public CustomerController()
        : this(new CustomerManager())
    {
    }

    public CustomerController(ICustomerManager customerMan)
{
    cm = customerMan;
}

    public ActionResult EditContact(ContactVM model,  IEnumerable<HttpPostedFileBase> Files, PageAction pageAction)
    {
        if (ModelState.IsValid)
        {
            InitializeContactVM(model); //throws an error
        }
    }

    private void InitializeContactVM(ContactVM model)
    {
        model.Customer = cm.GetViewFindCustomerDetails((int)model.CustomerId);
        model.ContactClassificationList = AddBlankToList(SelectLists.ContactClassifications(false)); 
        model.ContactSourceList = AddBlankToList(SelectLists.ContactSources(false));
    }
}

And my unit test looks like this: 我的单元测试看起来像这样:

public void Edit_Contact_Update_Existing_Contact()
{
    var dataManager = new Mock<IReferenceDataManager>();
    //dataManager.Setup(a=>a.GetContactClassifications()).Returns()
    var contact = InitializeContact();
    var contactvm = new ContactVM(contact);
    var fileMock = new Mock<HttpPostedFileBase>();
    var files = new[] {fileMock.Object};

    var mocManager = InitializeMocManagerContact();
    mocManager.Setup(a => a.GetContactById(It.IsAny<int>())).Returns(contact);
    mocManager.Setup(a => a.UpdateContact(It.IsAny<ContactVM>(), It.IsAny<string>())).Returns(contact);

    var controller = new CustomerController(mocManager.Object);
    var controllerContext = InitializeContext();
    controller.ControllerContext = controllerContext.Object;
    //  mocManager.CallBase = true;

    var result = controller.EditContact(contactvm, files, PageAction.Default) as ViewResult;
    var model = result.ViewData.Model as ContactVM;

    Assert.IsTrue(model.ContactId == contact.CONTACT_ID);
}

The problem is in the private method where it calls SelectLists.ContactClassifications(false), it then tries to hit the database. 问题出在私有方法中,它调用SelectLists.ContactClassifications(false),然后尝试命中数据库。

The SelectList class is defined like SelectList类定义如下

public static class SelectLists
{
    private static readonly ReferenceDataManager _dataManager = new ReferenceDataManager();

    public static SelectList ContactClassifications(bool includeDeleted)
    {
        var data = _dataManager.GetContactClassifications();
    }
}

and it is the line where it calls GetContactClassifications in the SelectList that it feels like I should be able to mock (if the method that calls it can't be mocked because it is static). 它是在SelectList中调用GetContactClassifications的行,它感觉我应该能够模拟(如果调用它的方法不能被模拟,因为它是静态的)。 This one does implement an interface. 这个实现了一个接口。

Even if there is some way that the private method in the Controller (InitialiseContactVM) could be mocked it would suit me. 即使有一些方法可以模拟Controller(InitialiseContactVM)中的私有方法,它也适合我。

Is there any way to achieve any of these things? 有没有办法实现这些东西?

应该重构SelectLists类,以允许您注入IReferenceDataManager而不是实例化一个。

Ideally, your DAL should not be made out of static methods, but normal objects that provide services injected though an interface into controllers or whatever needs it. 理想情况下,您的DAL不应该由静态方法构成,而是提供通过接口注入控制器或任何需要它的服务的普通对象。

But if you can't/don't want to change it, you the "standard" way to let you mock it would be to decouple the static method call from your controller. 但是,如果您不能/不想更改它,那么让您模拟它的“标准”方法是将静态方法调用与控制器分离。 It can be done by wrapping it in a class that contains the static call and implements an interface, that is injected in the controller, and therefore mocked out in the tests. 它可以通过将其包装在包含静态调用的类中来实现,并实现一个接口,该接口在控制器中注入,因此在测试中被模拟。 It's somewhat similar to testing a MessageBox call or the current system date/time. 它有点类似于测试MessageBox调用或当前系统日期/时间。

First create an interface that will contain your static method calls: 首先创建一个包含静态方法调用的接口:

public interface ISelectListsWrapper
{
    SelectList ContactClassifications(bool includeDeleted);
}

Then a class will implement it by calling the actual static method: 然后一个类将通过调用实际的静态方法来实现它:

public class SelectListsWrapper : ISelectListsWrapper
{
    public SelectList ContactClassifications(bool includeDeleted)
    {
        return SelectLists.ContactClassifications(includeDeleted);
    }
}

In the controller, you take an instance of this class in the constructor, save it to a local variable, and use that to call the static method though the wrapper: 在控制器中,您在构造函数中获取此类的实例,将其保存到局部变量,并使用它通过包装器调用静态方法:

private readonly ISelectListsWrapper selectLists;

public CustomerController(ICustomerManager customerMan, ISelectListsWrapper selectLists)
{
    cm = customerMan;
    this.selectLists = selectLists;
}

private void InitializeContactVM(ContactVM model)
{
    model.Customer = cm.GetViewFindCustomerDetails((int)model.CustomerId);
    model.ContactClassificationList = AddBlankToList(this.selectLists.ContactClassifications(false)); 
    model.ContactSourceList = AddBlankToList(this.selectLists.ContactSources(false));
}

Finally, in the test, you just pass a mock of the wrapper and setup it to return whatever makes sense to that test. 最后,在测试中,您只需传递包装器的模拟并设置它以返回对该测试有意义的任何内容。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM