使用IHttpActionResult返回类型和映射数据

时间:2017-11-23 14:53:46

标签: c# asp.net-mvc entity-framework unit-testing automapper

您好我需要测试我实现ApiController的API控制器,我不知道如何做到这一点,我知道UnitTesting的基础知识,但这对我来说有点过于复杂。此外,我不知道如何在单元测试中使用automapper,所以也许有人可以帮我解决这个问题

这是我的控制器:

   namespace Vidly.Controllers.Api
{
    public class CustomersController : ApiController
    {
        private ApplicationDbContext _context;

        public CustomersController(ApplicationDbContext _context)
        {
            _context = new ApplicationDbContext();
        }

        // GET /api/customers
        public IHttpActionResult GetCustomers(string query = null)
        {
            var customersQuery = _context.Customers
                .Include(c => c.MembershipType);

            if (!String.IsNullOrWhiteSpace(query))
                customersQuery = customersQuery.Where(c => c.Name.Contains(query));

            var customerDtos = customersQuery
                .ToList()
                .Select(Mapper.Map<Customer, CustomerDto>);

            return Ok(customerDtos);    
        }

        // GET /api/customers/1
        public IHttpActionResult GetCustomer(int id)
        {
            var customer = _context.Customers.SingleOrDefault(c => c.Id == id);

            if (customer == null)
                return NotFound();

            return Ok(Mapper.Map<Customer, CustomerDto>(customer));
        }

        // POST /api/customers
        [HttpPost]
        public IHttpActionResult CreateCustomer(CustomerDto customerDto)
        {
            if (!ModelState.IsValid)
                return BadRequest();

            var customer = Mapper.Map<CustomerDto, Customer>(customerDto);
            _context.Customers.Add(customer);
            _context.SaveChanges();

            customerDto.Id = customer.Id;
            return Created(new Uri(Request.RequestUri + "/" + customer.Id), customerDto);
        }

        // PUT /api/customers/1
        [HttpPut]
        public IHttpActionResult UpdateCustomer(int id, CustomerDto customerDto)
        {
            if (!ModelState.IsValid)
                return BadRequest();

            var customerInDb = _context.Customers.SingleOrDefault(c => c.Id == id);

            if (customerInDb == null)
                return NotFound();

            Mapper.Map(customerDto, customerInDb);

            _context.SaveChanges();

            return Ok();
        }

        // DELETE /api/customers/1
        [HttpDelete]
        public IHttpActionResult DeleteCustomer(int id)
        {
            var customerInDb = _context.Customers.SingleOrDefault(c => c.Id == id);

            if (customerInDb == null)
                return NotFound();

            _context.Customers.Remove(customerInDb);
            _context.SaveChanges();

            return Ok();
        }
    }
}

所以我需要如何开始,我需要这样的界面来模拟dbcontext:

public interface IAPICustomerRepository
{

    IHttpActionResult GetCustomers(string query = null);
    IHttpActionResult GetCustomer(int id);
    IHttpActionResult CreateCustomer(CustomerDto customerDto);
    IHttpActionResult UpdateCustomer(int id, CustomerDto customerDto);
    IHttpActionResult DeleteCustomer(int id);
}

或许我可以在没有嘲笑的情况下编写单元测试。

更新 在我用Nkosi的建议编辑我的代码后,我收到了这些错误

<Error>
    <Message>An error has occurred.</Message>
    <ExceptionMessage>
    An error occurred when trying to create a controller of type 'CustomersController'. Make sure that the controller has a parameterless public constructor.
    </ExceptionMessage>
    <ExceptionType>System.InvalidOperationException</ExceptionType>
    <StackTrace>
    at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) at System.Web.Http.Controllers.HttpControllerDescriptor.CreateController(HttpRequestMessage request) 
at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext()
    </StackTrace>
    <InnerException>
    <Message>An error has occurred.</Message>
    <ExceptionMessage>
    Type 'Vidly.Controllers.Api.CustomersController' does not have a default constructor
    </ExceptionMessage>
    <ExceptionType>System.ArgumentException</ExceptionType>
    <StackTrace>
    at System.Linq.Expressions.Expression.New(Type type) 
at System.Web.Http.Internal.TypeActivator.Create[TBase](Type instanceType) 
at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.GetInstanceOrActivator(HttpRequestMessage request, Type controllerType, Func`1& activator) 
at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)
    </StackTrace>
    </InnerException>
    </Error>

然后我创建一个默认构造函数(没有我所理解的参数)然后我得到另一个错误:

 <Error>
    <Message>An error has occurred.</Message>
    <ExceptionMessage>
    Object reference not set to an instance of an object.
    </ExceptionMessage>
    <ExceptionType>System.NullReferenceException</ExceptionType>
    <StackTrace>
    at Vidly.Controllers.Api.CustomersController.GetCustomers(String query) in C:\Users\Dovydas Petrutis\Desktop\vidly-mvc-5-master\Vidly\Controllers\Api\CustomersController.cs:line 26 
at lambda_method(Closure , Object , Object[] ) 
at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.<>c__DisplayClass10.<GetExecutor>b__9(Object instance, Object[] 
methodParameters) at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.Execute(Object instance, Object[] arguments) 
at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ExecuteAsync(HttpControllerContext controllerContext, IDictionary`2 arguments, CancellationToken cancellationToken) --- End of stack trace from previous location where exception was thrown --- 
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) 
at System.Web.Http.Controllers.ApiControllerActionInvoker.<InvokeActionAsyncCore>d__0.MoveNext() --- End of stack trace from previous location where exception was thrown --- 
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Web.Http.Controllers.ActionFilterResult.<ExecuteAsync>d__2.MoveNext() --- End of stack trace from previous location where exception was thrown --- 
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) 
at System.Web.Http.Dispatcher.HttpControllerDispatcher <SendAsync>d__1.MoveNext()
    </StackTrace>
    </Error>

现在问题出在哪里?

1 个答案:

答案 0 :(得分:2)

首先创建一个公开所需功能的服务,以便可以轻松替换/模拟测试并提高可维护性。

当你提到存储库时,你很接近。 IHttpActionResult是一个UI问题,因此您可以像这样重构您的界面。

public interface IAPICustomerRepository {
    IEnumerable<CustomerDto> GetCustomers(string query = null);
    CustomerDto GetCustomer(int id);
    int CreateCustomer(CustomerDto customerDto);
    CustomerDto UpdateCustomer(int id, CustomerDto customerDto);
    bool? DeleteCustomer(int id);
}

控制器现在在功能方面更加纤薄,不再关心EF / DbContext或Automapper。

public class CustomersController : ApiController {
    private IAPICustomerRepository repository;

    public CustomersController(IAPICustomerRepository repository) {
        this.repository = repository;
    }

    // GET /api/customers
    public IHttpActionResult GetCustomers(string query = null) {
        var customerDtos = repository.GetCustomers(query);
        return Ok(customerDtos);
    }

    // GET /api/customers/1
    public IHttpActionResult GetCustomer(int id) {
        var customer = repository.GetCustomer(id);
        if (customer == null)
            return NotFound();

        return Ok(customer);
    }

    // POST /api/customers
    [HttpPost]
    public IHttpActionResult CreateCustomer([FromBody]CustomerDto customerDto) {
        if (!ModelState.IsValid)
            return BadRequest();
        var id = repository.CreateCustomer(customerDto);
        customerDto.Id = id;
        return Created(new Uri(Request.RequestUri + "/" + id), customerDto);
    }

    // PUT /api/customers/1
    [HttpPut]
    public IHttpActionResult UpdateCustomer(int id, [FromBody]CustomerDto customerDto) {
        if (!ModelState.IsValid)
            return BadRequest();

        var updated = repository.UpdateCustomer(id, customerDto);
        if (updated == null)
            return NotFound();

        return Ok();
    }

    // DELETE /api/customers/1
    [HttpDelete]
    public IHttpActionResult DeleteCustomer(int id) {
        var deleted = repository.DeleteCustomer(id);
        if (deleted == null)
            return NotFound();

        return Ok();
    }
}

控制器现在依赖于抽象,您可以在单独测试控制器时模拟功能。

Automapper是一个实现问题,可以封装在抽象背后,因此在测试时它不是问题。

以下示例使用Moq模拟框架。您可以选择使用您选择的框架。

[TestClass]
public class CustomersController_Should {
    [TestMethod]
    public void GetCustomers() {
        //Arrange
        var fakeCustomers = new List<CustomerDto>{
            new CustomerDto{ Id = 1 }
        };
        var repository = new Mock<IAPICustomerRepository>();
        repository
            .Setup(_ => _.GetCustomers(It.IsAny<string>()))
            .Returns(fakeCustomers)
            .Verifiable();

        var controller = new CustomersController(repository.Object);

        //Act
        var result = controller.GetCustomers();

        //Assert
        repository.Verify();
        //..other assertions
    }

    //...Other tests
}

最初在控制器中的功能将封装在生产中的存储库实现中。