如何模拟依赖Web API控制器单元测试?

时间:2015-06-02 13:22:40

标签: c# unit-testing architecture

在我当前的MVC应用程序中,我构建了一系列命令对象来处理业务操作。这些业务操作将围绕服务端点进行。这些端点也将由MVC frond-end&一个Windows应用程序。每个业务操作都将调用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来协助层之间的依赖注入。我对业务进行了大量的单元测试&#39; CreateProjectAction&#39;测试该对象的预期行为。业务操作包含在一系列Web API服务端点中。我还想围绕我的MVC控制器编写测试,以确保它们按计划工作。

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

1 个答案:

答案 0 :(得分:0)

你的问题仍然有点不清楚。例如,InsertProjectDAOAction可能会实现接口IInsertProjectDAOAction,即使您的示例代码没有表明它确实存在。它还不清楚你的控制器示例中的CreateProjectCommand是什么,因为它不是你上面的一个示例元素。

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

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);
    }
}

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