在单元测试期间模拟特定方法
Mocking out a specific method during a unit test
我对单元测试比较陌生,对最小起订量也是全新的。我的任务是为一些预先存在的代码编写一些单元测试。
我正在努力使用以下方法,因为它会调用 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
的 class 来访问 属性 以调用 RunJob
方法,这有点奇怪。如果这确实是代码,那么您可能希望鼓励团队重新访问 Model
class.
其中一个建议是注入 Model
依赖项而不是创建它(或注入工厂并调出工厂进行创建)。这将是更可取的方式,但它不是方法上的微不足道的改变,实际上您的团队需要将其作为一种方法来接受,而不是您一次性进行改变。
如果您还没有使用 IOC 容器(AutoFac、CastleWindsor、Ninject),那么您可能需要考虑使用一个。它们将使切换到依赖注入更容易。如果你想手工做,那你也可以做,但是比较难。
在不了解更多 classes 的结构的情况下,很难给出一个完整的示例,但一种方法可能如下:
// 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);
}
这是对需要完成/测试的内容的简化,以证明一种方法。注入依赖项需要注意的问题之一是,很容易最终创建抽象的抽象,将实际逻辑越来越深地推入代码,却发现你只是将问题推得更深,而且你仍然不知道如何测试特定的逻辑部分,因为它本质上没有改变。
我对单元测试比较陌生,对最小起订量也是全新的。我的任务是为一些预先存在的代码编写一些单元测试。
我正在努力使用以下方法,因为它会调用 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
的 class 来访问 属性 以调用 RunJob
方法,这有点奇怪。如果这确实是代码,那么您可能希望鼓励团队重新访问 Model
class.
其中一个建议是注入 Model
依赖项而不是创建它(或注入工厂并调出工厂进行创建)。这将是更可取的方式,但它不是方法上的微不足道的改变,实际上您的团队需要将其作为一种方法来接受,而不是您一次性进行改变。
如果您还没有使用 IOC 容器(AutoFac、CastleWindsor、Ninject),那么您可能需要考虑使用一个。它们将使切换到依赖注入更容易。如果你想手工做,那你也可以做,但是比较难。
在不了解更多 classes 的结构的情况下,很难给出一个完整的示例,但一种方法可能如下:
// 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);
}
这是对需要完成/测试的内容的简化,以证明一种方法。注入依赖项需要注意的问题之一是,很容易最终创建抽象的抽象,将实际逻辑越来越深地推入代码,却发现你只是将问题推得更深,而且你仍然不知道如何测试特定的逻辑部分,因为它本质上没有改变。