2017-11-23 9 views
2

안녕하세요 APIController를 구현하는 API 컨트롤러를 테스트해야하며이 작업을 수행하는 방법을 알지 못합니다. UnitTesting의 기본 사항을 알고 있지만이 역시 조금 있습니다. 나를 위해 복잡한.Asp.net IHttpActionResult 반환 형식 및 매핑 된 데이터가있는 메서드에 대한 단위 테스트

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

가 어떻게, 내가 시작해야 할 좀 필요합니까 : 또한 나는 어쩌면 누군가가 여기에이

나를 도울 수, 단위 테스트에서 automapper 사용하는 방법을 모르는 내 컨트롤러입니다 이런 종류의 인터페이스를 모의 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); 
} 

아니면 내가 조롱하지 않고 단위 테스트를 작성할 수 있습니다.

UPDATE 나는 코시의 제안에 내 코드는 내가 이러한 오류 다음

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

어디에서 지금 문제가 될 수 있습니까?

+0

먼저 원하는 기능을 제공하는 서비스를 만들어 테스트를 위해 쉽게 교체/조롱되고 유지 관리 기능이 개선 될 수 있도록하십시오. automapper는 추상화 뒤에 캡슐화 될 수있는 구현 문제이므로 테스트 할 때 문제가되지 않습니다. – Nkosi

+1

추상화와 그 구현을 의존성 컨테이너에 등록 했습니까? 이 오류는 컨트롤러 또는 해당 종속성 중 하나를 해결할 때 오류로 인해 프레임 워크가 컨트롤러를 초기화 할 수 없음을 의미합니다. – Nkosi

+1

의존성 주입에 대한 자세한 내용은 https://docs.microsoft.com/en-us/aspnet/web-api/overview/advanced/dependency-injection을 참조하십시오. – Nkosi

답변

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 
} 

원래 컨트롤러에 있던 기능은 프로덕션 환경에서 저장소 구현에 캡슐화됩니다.