简体   繁体   中英

Mock Request Object for Web API unit test

I want to unit test my web API controller. I have a problem with one of my action method (POST) which is need value from Request object, to get the controller name. I'm using NSubtitute, FluentAssertions to support my unit test

This is my controller code looks like:

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

And the base class for my controller :

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

And this is my unit test for success create scenario:

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

I don't know if there is any way to extract Request object from the controller, or maybe is there any way to mock it on the unit test? Right now this code Request.GetRouteData() return null on the unit test.

you can make an interface for getting Request Data(pass Request object to it). Implement that interface and use as dependency in your Controller. Then you can easily mock this interface implementation in your unit tests.

I've finally find a way to solve this. So basically I have to create some configuration related stuff to make my unit test works.

I create a helpers class for this

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

Then passing my test controller on my test setup

[SetUp]
public void SetUp()
{
    clientsRepository = Substitute.For<IRepository<ContactIndex>>();
    _baseController = new ClientsController(clientsRepository);

    Helpers.SetupControllerForTests(_baseController);
}

I don't know if this is a best way to do it, but I prefer this way instead of create a new interface and inject it to my controller.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM