【问题标题】:How can mock dependencies to Web API controller unit test?如何模拟对 Web API 控制器单元测试的依赖关系?
【发布时间】:2015-06-02 13:22:40
【问题描述】:

在我当前的 MVC 应用程序中,我构建了一系列命令对象来处理业务操作。这些业务操作将围绕服务端点进行。这些端点也将被 MVC 前端和 Windows 应用程序使用。每个业务操作都会调用 DAO 操作,而 DAO 操作又会调用所需的数据访问存储库以成功执行业务操作。我在下面列出了一个示例操作。

商务行动

public class CreateProjectAction
{
    IInsertProjectDAOAction InsertProjectDAOAction { get; set; }

    public void Execute()
    {
        // Does some business validation & other logic before
        //  calling the DAO action
        InsertProjectDAOAction.Execute();
    }
}

DAO 操作

public interface IInsertProjectDAOAction
{
    void Execute();
}

public class InsertProjectDAOAction
{
    IProjectRepository ProjectRepository { get; set; }

    public void Execute()
    {
        ProjectRepository.Insert();
    }
}

项目存储库

public interface IProjectRepository 
{
    void Insert(Project proj);

    // other db methods would be listed here
}

public class ProjectRepository
{
    public void Insert(Project proj)
    {
        // Insert into the data store
    }
}

控制器

[HttpPost]
public IHttpActionResult Create(NewProjectModel newProjectModel)
{
    var cmdArgs = Mapper.Map<CreateProjectCommand.CreateProjectCommandArgs>(newProjectModel);

    var action = new CreateProjectCommand(UserId, cmdArgs);
    action.Execute();

    if(action.IsSuccessful)
        return Ok(project)
    else
        return InternalServerError(action.Exception);
}

单元测试

public void InsertWith_ExistingProjectName_Returns_ServerError()
{
    var arg = new CreateProjectCommandArgs(){ .... };
    var cmd = CreateProjectAction(args);
    action.Execute();

    Assert.That(action.IsSuccessful, Is.False);
    Assert.That(action.Exception, Is.TypeOf<UniqueNameExcepton>());
}

我正在使用 Ninject 来协助层之间的依赖注入。我围绕业务“CreateProjectAction”进行了一系列单元测试,以测试该对象的预期行为。业务操作围绕一系列 Web API 服务端点进行包装。我还想围绕我的 MVC 控制器编写测试,以确保它们按计划工作。

到目前为止,我喜欢这个架构,但是在为 mvc 控制器编写单元测试时,无法弄清楚如何在业务操作中模拟 DAO 操作属性。我很想听听建议、其他观点等...

【问题讨论】:

  • 您确实需要举一个控制器操作的示例,并尝试进行测试以获得有用的答案。如果不是很明显,您可能还想指出您正在使用的模拟框架(如果有的话)。目前还不清楚为什么你需要一个模拟属性InsertProjectDAOAction=SomeTestClassThatImplementsIProjectRepository???这是InsertProjectDAOAction 应该实现IInsertProjectDAOAction 吗?如果您使用的是模拟,您是否只需要模拟界面并使用Execute 方法?我想我要说的是你在问什么
  • 您的问题是什么?到目前为止,您尝试过什么?

标签: c# unit-testing architecture


【解决方案1】:

你的问题还是有点不清楚。例如,InsertProjectDAOAction 似乎很可能实现了接口IInsertProjectDAOAction,即使您的示例代码并未表明它确实如此。还不清楚您的控制器示例中的 CreateProjectCommand 是什么,因为它不是您上面的示例元素之一。

也就是说,您可以采取的一种方法是将命令的创建推迟到工厂并将工厂注入您的控制器(通过代码中的 Ninject 和单元测试中的 Mock)。这允许您设置模拟链。您模拟工厂并让它返回您感兴趣的操作的模拟,然后您可以设置它来做任何您想做的事情。在非常基本的层面上,这可能如下所示:

public interface ICommandFactory {
    IInsertProjectDAOAction CreateInsertProjectAction(int userId);
}

public class CommandFactory : ICommandFactory{
    public IInsertProjectDAOAction CreateInsertProjectAction(int userId) {
        return new InsertProjectDAOAction(/* userId???? */);
    }
}

控制器会做这样的事情来使用工厂:

public IHttpActionResult Create(/* ... */) {
    var action = _commandFactory.CreateInsertProjectAction(1234);
    action.Execute();
    // ...
}

测试看起来像:

[Test]
public void MyTest() {
    var factoryMock = new Mock<ICommandFactory>();
    var commandMock = new Mock<IInsertProjectDAOAction>();

    factoryMock.Setup(x => x.CreateInsertProjectAction(It.IsAny<int>())).Returns(commandMock.Object);
    commandMock.Setup(x => x.Execute()).Throws(new InvalidOperationException("Random failure"));

    var controller = new MyController(factoryMock.Object);

    try {
        controller.Create(/* ... */);
        Assert.Fail();
    }
    catch (InvalidOperationException ex) {
        Assert.AreEqual("Random failure", ex.Message);
    }
}

这是您可以采用的一般方法。但是,正如我所说,这可能不适合您的情况,因为您的问题不清楚。我也忽略了其他关于如何创建/测试控制器的问题,因为这似乎不是你的问题......

【讨论】:

    猜你喜欢
    • 2019-12-14
    • 2020-01-05
    • 2015-02-04
    • 1970-01-01
    • 1970-01-01
    • 2016-02-19
    • 1970-01-01
    • 2016-07-08
    • 2018-06-28
    相关资源
    最近更新 更多