如何模拟对Web API控制器单元测试的依赖关系

本文关键字:控制器 API 单元测试 关系 依赖 Web 何模拟 模拟 | 更新日期: 2023-09-27 17:52:17

在我当前的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操作属性。我很乐意听取建议、其他观点等。。。

如何模拟对Web API控制器单元测试的依赖关系

您的问题仍然有点不清楚。例如,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);
    }
}

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