【问题标题】:Mock Request Object for Web API unit testWeb API 单元测试的模拟请求对象
【发布时间】:2015-05-18 07:05:53
【问题描述】:

我想对我的 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

【问题讨论】:

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


    【解决方案1】:

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

    【讨论】:

      【解决方案2】:

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

      我为此创建了一个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);
      }
      

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

      【讨论】:

      • 这不是最好的方法。 DependencyInjection 是更推荐的方法。
      • 但这是否适合我的要求?因为我只需要它来获取控制器名称。也许您可以给出一些代码示例,因为我认为创建一个仅用于传递Request 对象的接口是没有用的。但再次重申,这只是我的意见,如果您能提供一些新信息来启发我,我将不胜感激。
      • 它不仅适用于请求对象,您还可以封装其他依赖项,就像您对 ControllerContext 所做的那样。当您的控制器变得复杂时,它会更容易维护
      • @realnero 这种模拟Request的方式在官方文档中提到:docs.microsoft.com/en-us/aspnet/web-api/overview/…
      猜你喜欢
      • 2018-10-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-17
      • 2019-06-03
      • 1970-01-01
      相关资源
      最近更新 更多