C# 如何模拟对Web API控制器的依赖项进行单元测试?

C# 如何模拟对Web API控制器的依赖项进行单元测试?,c#,unit-testing,architecture,C#,Unit Testing,Architecture,在我当前的MVC应用程序中,我设计了一系列命令对象来处理业务操作。这些业务操作将围绕服务端点展开。MVC前端和windows应用程序也会使用这些端点。每个业务操作都将调用DAO操作,DAO操作反过来调用所需的数据访问存储库以成功执行业务操作。我在下面列出了一个示例操作 业务行动 public class CreateProjectAction { IInsertProjectDAOAction InsertProjectDAOAction { get; set; } publi

在我当前的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();
    }
}
public interface IInsertProjectDAOAction
{
    void Execute();
}

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

    public void Execute()
    {
        ProjectRepository.Insert();
    }
}
DAO行动

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

    public void Execute()
    {
        // Does some business validation & other logic before
        //  calling the DAO action
        InsertProjectDAOAction.Execute();
    }
}
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);
}
[HttpPost]
公共IHttpActionResult创建(新建项目模型新建项目模型)
{
var cmdArgs=Mapper.Map(newProjectModel);
var action=new CreateProjectCommand(UserId,cmdArgs);
action.Execute();
if(action.issucessful)
返回Ok(项目)
其他的
返回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>());
}
public void InsertWith_ExistingProjectName_返回_ServerError()
{
var arg=new CreateProjectCommandArgs(){….};
var cmd=CreateProjectAction(args);
action.Execute();
Assert.That(action.issusccessful,Is.False);
Assert.That(action.Exception,Is.TypeOf());
}
我使用Ninject来协助层之间的依赖注入。我围绕业务“CreateProjectAction”进行了一系列单元测试,以测试该对象的预期行为。业务操作围绕一系列Web API服务端点进行包装。我还想围绕我的MVC控制器编写测试,以确保它们按计划工作


到目前为止,我喜欢architecure,但在为mvc控制器编写单元测试时,很难弄清楚如何在业务操作中模拟DAO操作属性。我很想听听你的建议,其他观点等等。

你的问题还是有点不清楚。例如,
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);
    }
}
[测试]
公共无效MyTest(){
var factoryMock=new Mock();
var commandMock=new Mock();
Setup(x=>x.CreateInsertProjectAction(It.IsAny()).Returns(commandMock.Object);
Setup(x=>x.Execute()).Throws(新的InvalidOperationException(“随机失败”));
var controller=新的MyController(factoryMock.Object);
试一试{
controller.Create(/*…*/);
Assert.Fail();
}
捕获(无效操作异常ex){
Assert.AreEqual(“随机故障”,例如消息);
}
}

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

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