在单元测试期间模拟一个特定的方法

本文关键字:一个 方法 单元测试 模拟 | 更新日期: 2023-09-27 17:50:52

我对单元测试比较陌生,对Moq也是全新的。我的任务是为一些已有的代码编写一些单元测试。

我正在努力使用以下方法,因为它调用model.importing.RunJob,顾名思义,它启动了一个工作。

显然,我不想让它实际完成工作,我只想知道它被称为方法。

我该如何实现这一点(理想情况下避免任何代码更改):

public ActionResult ImportsAndLoads(ImportsAndLoadsViewModel importsAndLoadsModel)
{
    Initialise(importsAndLoadsModel);
    LoadData(importsAndLoadsModel);
    if (importsAndLoadsModel.ActionToPerform == "RunJob")
    {
        using (var model = new Model())
        {
            List<Message> lstMessage;
            model.importing.RunJob((Jobs)Enum.ToObject(typeof(Jobs), importsAndLoadsModel.SelectedJob), out lstMessage);
            importsAndLoadsModel.lstMessages = lstMessage;
        }
    }
    return View(importsAndLoadsModel);
}

在单元测试期间模拟一个特定的方法

正如您的问题评论中所讨论的那样,您无法真正以当前形式使用Moq单独测试您的ImportsAndLoads方法。如果你使用的是一个足够高的Visual Studio版本,那么你可以使用Shims来测试这个方法,但是如果你还没有在你的项目中使用它,这可能不是正确的方法。

现在看来,你的代码有点混乱。这有点奇怪,有一个叫做Model的类,你正在创建,为了访问一个属性,为了调用RunJob方法。如果这确实是代码,那么您可能希望鼓励团队重新访问Model类。

其中一个建议是你注入你的Model依赖而不是创建它(或者注入一个工厂并调用工厂进行创建)。这将是更可取的方法,但是这不是一个微不足道的方法更改,并且您的团队需要将其作为一种方法来接受,而不是将其作为一次性更改。

如果你还没有使用IOC容器(AutoFac, CastleWindsor, Ninject),那么你可能需要考虑使用一个。它们将使切换到依赖注入更容易。如果你想用手做,那么你可以做,但它更难。

在不了解更多类的结构的情况下,很难给出一个完整的示例,但一种方法可能是:

// Create an importer interface
public interface IImporter {
    void RunJob(Jobs job, out List<Message> listMessages);
}
// Implement it in a concrete class
public class Importer : IImporter{
    public void RunJob(Jobs job, out List<Message> listMessages) {
        // Do whatever it is it does.
    }
}
// Modify the constructor of your controller to allow the IImporter
// interface to be injected.  Default to null if not supplied, so that
// it can still be crated without parameters by the default MVC plumbing
public class ImportController : Controller
{
    private IImporter _importer;
    public ImportController(IImporter importer = null) {
        // If importer not injected, created a concrete instance
        if (null == importer) {
            importer = new Importer();
        }
        // Save dependency for use in actions
        _importer = importer;
    }
    // Use the injected importer in your action, rather than creating a model
    public ActionResult ImportsAndLoads(ImportsAndLoadsViewModel importsAndLoadsViewModel)
    {
        List<Message> listMsgs;
        _importer.RunJob(Jobs.One, out listMsgs);
        importsAndLoadsViewModel.lstMessages = listMsgs;
        return View(importsAndLoadsViewModel);
    }
}

这将允许您编写一个测试来验证importsAndLoadsViewModel已按预期更新,使用如下测试:

[Test]
public void TestModelMessagesAreUpdatedFromJobRunner() {
    var mockImporter = new Mock<IImporter>();
    List<Message> expectedMessages = new List<Message>();
    mockImporter.Setup(x=>x.RunJob(It.IsAny<Jobs>(), out expectedMessages));
    var model = new ImportsAndLoadsViewModel();
    // Inject the mocked importer while constructing your controller
    var sut = new ImportController(mockImporter.Object);
    // call the action you're testing on your controller
    ViewResult response = (ViewResult)sut.ImportsAndLoads(model);
    // Validate the the model has been updated to have the messages
    // returned by the mocked importer.
    Assert.AreEqual(expectedMessages, 
                    ((ImportsAndLoadsViewModel)response.Model).lstMessages);
}

这简化了需要做什么/测试什么来演示一种方法。注入依赖需要注意的一个问题是,很容易最终创建抽象的抽象,将实际的逻辑越来越深入到你的代码中,却发现你只是把问题推得越来越深,你仍然不知道如何测试特定的逻辑,因为本质上它没有改变。