用于Web API单元测试的模拟请求对象

时间:2015-05-18 07:05:54

标签: c# asp.net unit-testing asp.net-web-api

我想对我的Web API控制器进行单元测试。我的一个操作方法(POST)有问题,需要来自Request对象的值,以获取控制器名称。 我使用NSubtitute,FluentAssertions来支持我的单元测试

这是我的控制器代码:

public class ReceiptsController : BaseController
{
    public ReceiptsController(IRepository<ReceiptIndex> repository) : base(repository) { }
    ..... Other action code

    [HttpPost]
    public IHttpActionResult PostReceipt(string accountId, [FromBody] ReceiptContent data, string userId = "", string deviceId = "", string deviceName = "")
    {
        if (data.date <= 0)
        {
            return BadRequest("ErrCode: Save Receipt, no date provided");
        }

        var commonField = new CommonField()
        {
            AccountId = accountId,
            DeviceId = deviceId,
            DeviceName = deviceName,
            UserId = userId
        };
        return PostItem(repository, commonField, data);
    }
}

我控制器的基类:

public abstract class BaseController : ApiController
{
    protected IRepository<IDatabaseTable> repository;

    protected BaseController(IRepository<IDatabaseTable> repository)
    {
       this.repository = repository;
    }

    protected virtual IHttpActionResult PostItem(IRepository<IDatabaseTable> repo, CommonField field, IContent data)
    {
        // How can I mock Request object on this code part ???
        string controllerName = Request.GetRouteData().Values["controller"].ToString();

        var result = repository.CreateItem(field, data);

        if (result.Error)
        {
            return InternalServerError();
        }

        string createdResource = string.Format("{0}api/accounts/{1}/{2}/{3}", GlobalConfiguration.Configuration.VirtualPathRoot, field.AccountId,controllerName, result.Data);
        var createdData = repository.GetItem(field.AccountId, result.Data);

        if (createdData.Error)
        {
            return InternalServerError();
        }
        return Created(createdResource, createdData.Data);
    }
}

这是我成功创建场景的单元测试:

[Test]
public void PostClient_CreateClient_ReturnNewClient()
{
    // Arrange
    var contentData = TestData.Client.ClientContentData("TestBillingName_1");
    var newClientId = 456;
    var expectedData = TestData.Client.ClientData(newClientId);

    clientsRepository.CreateItem(Arg.Any<CommonField>(), contentData)
         .Returns(new Result<long>(newClientId)
         {
            Message = ""
         });

     clientsRepository.GetItem(accountId, newClientId)
         .Returns(new Result<ContactIndex>(expectedData));

     // Act
     var result = _baseController.PostClient(accountId, contentData, userId);

     // Asserts
      result.Should().BeOfType<CreatedNegotiatedContentResult<ContactIndex>>()
                .Which.Content.ShouldBeEquivalentTo(expectedData);
 }

我不知道是否有任何方法可以从控制器中提取Request对象,或者有没有办法在单元测试中模拟它? 现在,此代码Request.GetRouteData()在单元测试中返回null

2 个答案:

答案 0 :(得分:5)

您可以创建一个获取请求数据的接口(将Request对象传递给它)。实现该接口并在Controller中用作依赖项。然后,您可以在单元测试中轻松模拟此接口实现。

答案 1 :(得分:1)

我终于找到了解决这个问题的方法。所以基本上我必须创建一些配置相关的东西,以使我的单元测试工作。

我为此

创建了一个helpers
public static class Helpers
    {
        public static void SetupControllerForTests(ApiController controller)
        {
            var config = new HttpConfiguration();
            var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/api/products");
            var route = config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}");
            var routeData = new HttpRouteData(route, new HttpRouteValueDictionary { { "controller", "products" } });

            controller.ControllerContext = new HttpControllerContext(config, routeData, request);
            controller.Request = request;
            controller.Request.Properties[HttpPropertyKeys.HttpConfigurationKey] = config;
        }
    }

然后在测试setup

上传递我的测试控制器
[SetUp]
public void SetUp()
{
    clientsRepository = Substitute.For<IRepository<ContactIndex>>();
    _baseController = new ClientsController(clientsRepository);

    Helpers.SetupControllerForTests(_baseController);
}

我不知道这是否是最佳方式,但我更喜欢这种方式,而不是创建一个新界面并将其注入我的控制器。