Entenda a diferença entre os Action Results IHttpActionResult e HttpResponseMessage

Aqui no blog tem dois posts sobre teste unitário para controllers no ASP.NET Web API 2, um que explica como fazer usando o HttpResponseMessage e outro que explica usando o IHttpActionResult.

Nesse post vou explicar a diferença entre ambos para que seja possível entender melhor quando usar cada uma deles.

No ASP .NET Web API 2 temos dois tipos de action results que correspondem a uma resposta HTTP da controller: HttpResponseMessage e IHttpActionResult.

Dependendo de qual for escolhido, a controller usa maneiras diferentes de criar a resposta HTTP.

No HttpResponseMessage a controller retorna diretamente uma resposta HTTP. Isto é feito através da criação de um objeto resposta do HttpResponseMessage que possui suas propriedades preenchidas semelhante a uma resposta HTTP convencional.  É possível configurar o conteúdo da resposta, o tipo do dado retornado, ou até mesmo configurar os cabeçalhos dela.

HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created);
response.Content = new StringContent("Blog", Encoding.UTF8);
response.Headers.Location = new Uri("https://luizpauloprado.com.br");
response.Headers.Date = new DateTimeOffset(DateTime.UtcNow);
response.Headers.CacheControl = new CacheControlHeaderValue() { MaxAge = TimeSpan.FromMinutes(30) };

return response;

Em resumo, pode-se dizer que o HttpResponseMessage possibilita um controle bem maior sobre a criação de um resposta HTTP na controller.

Já no IHttpActionResult temos o mesmo conceito de resposta HTTP, só que de maneira mais simplificada e encapsulada. Ele basicamente representa uma factory de HttpResponseMessage.

public IHttpActionResult Get(int id)
{
    var sampleObject = _repository.Get(id);

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

    return Ok(sampleObject);
}

Na documentação oficial do ASP .NET Web API 2 tem uma explicação mais detalhada sobre tudo isso. Vale a pena conferir.

Teste Unitário para Controllers no ASP.NET Web API 2 com IHttpActionResult

Neste artigo vou explicar como programar testes unitários para controllers do ASP.NET Web API 2 que retornam seus dados em uma resposta HTTP do tipo IHttpActionResult.

O projeto base para este artigo pode ser encontrado no post que explica testes unitários para controllers do ASP.NET Web API 2 que retornam seus dados em uma resposta HTTP do tipo HttpResponseMessage.

Aqui no blog também tem outro post que explica a diferença entre o IHttpActionResult e o HttpResponseMessage.

Assim como no outro artigo, devemos iniciar adicionando um projeto de testes na nossa solução do Visual Studio. Para adicioná-lo basta clicar com o botão direito do mouse na sua solution e seguir os passos Add > New Project > Test > Unit Test Project.

Um ponto importante é se lembrar de adicionar o pacote Microsft.AspNet.WebApi.Core para que seja possível criar os objetos de controller na nossa classe de testes.

Acessando o projeto exemplo no GitHub vocês podem conferir que o método Initialize() faz as inicialização do repositório já com alguns dados de exemplo.

O primeiro teste irá validar o comportamento de listar todos os registros do nosso repositório através do método Get() da controller.

Nele iremos instanciar o nosso controller e passar como parâmetro um objeto de repositório com os nossos dados de exemplo. Em seguida iremos chamar o método Get() e o seu retorno será armazenado na variável result.

[TestMethod]
public void ShouldGetAllCars()
{
    var controller = new CarController(_repository);
    controller.Request = new HttpRequestMessage();
    controller.Configuration = new HttpConfiguration();

    var result = controller.Get() as OkNegotiatedContentResult<IEnumerable<Car>>;

    Assert.IsNotNull(result);
    Assert.IsNotNull(result.Content);
    Assert.AreEqual(3, result.Content.Count());
}

O resultado do tipo IHttpActionResult, para ser devidamente testado, precisa ser armazenado numa variável do tipo OkNegotiatedContentResult. Esse tipo de objeto corresponde ao conteúdo do resultado sendo considerado como uma reposta HTTP do tipo Ok.

Mais tipos de objetos podem ser encontrados no namespace System.Web.Http.Results. Por exemplo, uma resposta HTTP do tipo Created pode ser testada usando o CreatedNegotiatedResult, ou uma resposta HTTP do tipo NotFound pode ser testada usando o NotFoundResult. Na documentação oficial do ASP.NET Web API 2 temos uma explicação mais detalhada sobre esses objetos.

Para validar o resultado iremos usar o Assert.IsNotNull que verifica se a resposta retornada não é vazia. Em seguida validamos que o conteúdo dessa reposta também não é vazio. A última validação usa o Assert.AreEqual para verificar se os registros retornados possuem a mesma quantidade que foi configurada ao criar o objeto de repositório, neste caso, se o result.Content.Count() é igual a 3.

Outro tipo teste interessante para se fazer é validar se o tipo de reposta representa um código HTTP de erro como NotFound ou BadRequest. No nosso projeto de teste temos um método de ShouldReturnNotFound() para validar que um GET retornou o código de status HTTP correspondente a um NotFound.

[TestMethod]
public void ShouldReturnNotFound()
{
    var controller = new CarController(_repository);
    controller.Request = new HttpRequestMessage();
    controller.Configuration = new HttpConfiguration();

    var result = controller.Get("Ferrari F7");

    Assert.IsInstanceOfType(result, typeof(NotFoundResult));
}

Os demais métodos de teste para cada ação do nosso controller seguem a mesma estrutura dos exemplos mostrados. A diferença está no que está sendo validado. Pode ser um campo do objeto, pode ser a quantidade de registros retornados, ou até mesmo se o objeto é nulo. Acompanhe abaixo a classe de testes com todos os métodos.

[TestMethod]
namespace LuizPauloPradoBlog.WebApi.Tests
{
    [TestClass]
    public class CarControllerTest
    {
        private ICarRepository _repository;

        [TestInitialize]
        public void Initialize()
        {
            var carSamples = new List<Car>();
            carSamples.Add(new Car() { Name = "Fiesta", Model = "Fiesta SE", YearOfManufacture = 2015 });
            carSamples.Add(new Car() { Name = "Golf", Model = "Golf Sport", YearOfManufacture = 2015 });
            carSamples.Add(new Car() { Name = "Civic", Model = "Civic S", YearOfManufacture = 2016 });

            _repository = new CarRepository(carSamples);
        }

        [TestMethod]
        public void ShouldGetAllCars()
        {
            var controller = new CarController(_repository);
            controller.Request = new HttpRequestMessage();
            controller.Configuration = new HttpConfiguration();

            var result = controller.Get() as OkNegotiatedContentResult<IEnumerable<Car>>;

            Assert.IsNotNull(result);
            Assert.IsNotNull(result.Content);
            Assert.AreEqual(3, result.Content.Count());
        }

        [TestMethod]
        public void ShouldGetCar()
        {
            var controller = new CarController(_repository);
            controller.Request = new HttpRequestMessage();
            controller.Configuration = new HttpConfiguration();

            var result = controller.Get("Fiesta SE") as OkNegotiatedContentResult<Car>;

            Assert.IsNotNull(result);
            Assert.IsNotNull(result.Content);
            Assert.AreEqual(result.Content.Model, "Fiesta SE");
        }

        [TestMethod]
        public void ShouldPostCar()
        {
            var controller = new CarController(_repository);
            controller.Request = new HttpRequestMessage();
            controller.Configuration = new HttpConfiguration();

            var newCar = new Car() { Name = "IX 35", Model = "IX 35 Special", YearOfManufacture = 2016 };

            var result = controller.Post(newCar) as CreatedNegotiatedContentResult<Car>;

            Assert.IsNotNull(result);
            Assert.IsNotNull(result.Content);
            Assert.AreEqual(result.Content.Name, newCar.Name);
            Assert.AreEqual(result.Content.Model, newCar.Model);
            Assert.AreEqual(result.Content.YearOfManufacture, newCar.YearOfManufacture);
        }

        [TestMethod]
        public void ShouldReturnNotFound()
        {
            var controller = new CarController(_repository);
            controller.Request = new HttpRequestMessage();
            controller.Configuration = new HttpConfiguration();

            var result = controller.Get("Ferrari F7");

            Assert.IsInstanceOfType(result, typeof(NotFoundResult));
        }
    }
}

O último passo é conferir se está tudo funcionando corretamente e executar todos os testes do projeto.

Captura de Tela 2016-06-13 às 19.39.03

O projeto completo de exemplo pode ser encontrado no meu GitHub.

Se quiserem conhecer mais sobre mim, basta ir aqui no blog no menu quem sou e conferir também o meu LinkedIn.

Se ficou alguma dúvida, ou se algo não funcionou, deixa um comentário aqui no blog que esclareço as dúvidas.

Teste Unitário para Controllers no ASP.NET Web API 2 com HttpResponseMessage

Neste artigo vou explicar como desenvolver testes unitários para controllers do ASP.NET Web API 2 que retornam  seus dados em uma resposta HTTP do tipo HttpResponseMessage.

No ASP.NET Web API 2 os métodos no controller podem retornar, por exemplo, dois tipos de resposta HTTP: HttpResponseMessage ou IHttpActionResult. Para entender melhor a diferença entre eles é só dar um olhada nesse outro post que explica melhor como cada um funciona.

Para iniciar vamos criar um objeto de exemplo para usar no repositório.

namespace LuizPauloPradoBlog.Repository.Model
{
    public class Car
    {
        public string Model { get; set; }
        public string Name { get; set; }
        public int YearOfManufacture { get; set; }
    }
}

Depois da criação do objeto de exemplo, basta criar um repositório simples, sem envolver muitos conceitos avançados, que vai persistir nossos dados.

namespace LuizPauloPradoBlog.Repository
{
    public class CarRepository : ICarRepository
    {
       private List<Car> _cars;

        public CarRepository(List<Car> cars)
        {
            _cars = cars;
        }

        public CarRepository()
        {
            _cars = new List<Car>();
        }

        public void Add(Car car)
        {
            _cars.Add(car);
        }

        public Car Get(string model)
        {
            return _cars.FirstOrDefault(x => x.Model == model);
        }

        public IEnumerable<Car> GetAll()
        {
            return _cars;
        }
    }
}

Terminado o repositório, será necessário criar um controller para acessar esses métodos e criar as respostas HTTP usando HttpResponseMessage.

namespace LuizPauloPradoBlog.Repository.Model
namespace LuizPauloPradoBlog.WebApi.Controllers
{
    public class CarController : ApiController
    {
        private ICarRepository _repository;

        public CarController(ICarRepository repository)
        {
            _repository = repository;
        }

        public HttpResponseMessage Get()
        {
            var cars = _repository.GetAll();

            return Request.CreateResponse(cars);
        }

        public HttpResponseMessage Get(string model)
        {
            var car = _repository.Get(model);

            if (car == null)
                return Request.CreateResponse(HttpStatusCode.NotFound);

            return Request.CreateResponse(car);
        }

        public HttpResponseMessage Post(Car car)
        {
            _repository.Add(car);

            return Request.CreateResponse(HttpStatusCode.Created, car);
        }
    }
}

Nosso controller contém alguns métodos de exemplo para listar todos os registros com o Get(), localizar um registro por modelo do carro com o Get(string model) e cadastrar um registro novo com o Post(Car car).

É neste ponto que chegamos onde queremos. Toda estrutura básica de um projeto está criada e finalmente podemos adicionar o projeto de testes que irá validar o comportamento do nosso controller.

Pará adicioná-lo basta clicar com o botão direito do mouse na sua solution e seguir os passos Add > New Project > Test > Unit Test Project.

Captura de Tela 2016-06-07 às 17.55.14

Após a criação do projeto de testes, um passo muito importante é se lembrar de adicionar o pacote Microsft.AspNet.WebApi.Core para que seja possível criar os objetos de controller na nossa classe de testes.

Nosso primeiro teste vai validar o comportamento de selecionar um registro do nosso repositório através do método Get(string model) do controller.

[TestMethod]
public void ShouldGetCar()
{
    var controller = new CarController(_repository);
    controller.Request = new HttpRequestMessage();
    controller.Configuration = new HttpConfiguration();

    var result = controller.Get("Fiesta SE");

    Car car;

    Assert.IsTrue(result.TryGetContentValue<Car>(out car));
    Assert.AreEqual(car.Model, "Fiesta SE");
}

Primeiramente iremos instanciar  o nosso controller e passar como parâmetro um objeto de repositório com os nossos dados de exemplo. Em seguida iremos chamar o método Get(string model) e o seu retorno será armazenado na variável result. Para validar o resultado iremos usar o Assert.IsTrue chamando o método TryGetCurrentValue no nosso objeto result para converter o conteúdo em um objeto do tipo Car. A validação seguinte verifica se objeto car é de fato o registro que foi selecionado no Get(string model) do controller.

Os demais métodos de teste para cada ação do nosso controller seguem a mesma estrutura do exemplo mostrado acima. A diferença está no que está sendo validado. Pode ser um campo do objeto, pode ser a quantidade de registros retornados, ou até mesmo se o objeto é nulo. Acompanhe abaixo a classe de testes com todos os métodos.

namespace LuizPauloPradoBlog.WebApi.Tests
{
    [TestClass]
    public class CarControllerTest
    {
        private ICarRepository _repository;

        [TestInitialize]
        public void Initialize()
        {
            var carSamples = new List<Car>();
            carSamples.Add(new Car() { Name = "Fiesta", Model = "Fiesta SE", YearOfManufacture = 2015 });
            carSamples.Add(new Car() { Name = "Golf", Model = "Golf Sport", YearOfManufacture = 2015 });
            carSamples.Add(new Car() { Name = "Civic", Model = "Civic S", YearOfManufacture = 2016 });

            _repository = new CarRepository(carSamples);
        }

        [TestMethod]
        public void ShouldGetAllCars()
        {
            var controller = new CarController(_repository);
            controller.Request = new HttpRequestMessage();
            controller.Configuration = new HttpConfiguration();

            var result = controller.Get();

            IEnumerable<Car> cars;

            Assert.IsTrue(result.TryGetContentValue<IEnumerable<Car>>(out cars));
            Assert.IsNotNull(cars);
            Assert.AreEqual(3, cars.Count());
        }

        [TestMethod]
        public void ShouldGetCar()
        {
            var controller = new CarController(_repository);
            controller.Request = new HttpRequestMessage();
            controller.Configuration = new HttpConfiguration();

            var result = controller.Get("Fiesta SE");

            Car car;

            Assert.IsTrue(result.TryGetContentValue<Car>(out car));
            Assert.AreEqual(car.Model, "Fiesta SE");
        }

        [TestMethod]
        public void ShouldPostCar()
        {
            var controller = new CarController(_repository);
            controller.Request = new HttpRequestMessage();
            controller.Configuration = new HttpConfiguration();

            var newCar = new Car() { Name = "IX 35", Model = "IX 35 Special", YearOfManufacture = 2016 };

            var result = controller.Post(newCar);

            Car car;

            Assert.IsTrue(result.TryGetContentValue<Car>(out car));
            Assert.IsNotNull(car);
            Assert.AreEqual(car.Name, newCar.Name);
            Assert.AreEqual(car.Model, newCar.Model);
            Assert.AreEqual(car.YearOfManufacture, newCar.YearOfManufacture);
        }
    }
}

O último passo é executar todos os testes e conferir se tudo funcionou corretamente.

Captura de Tela 2016-06-07 às 18.19.49

O projeto completo de exemplo pode ser encontrado no meu GitHub.

Se quiserem conhecer mais sobre mim, basta ir aqui no blog no menu quem sou e conferir também o meu LinkedIn.

Aqui no blog também tem o artigo que explica como fazer esses mesmos testes em um projeto ASP.NET Web API 2 que retorna os dados em um IHttpActionResult.

Se ficou alguma dúvida, ou se algo não funcionou, deixa um comentário aqui no blog que esclareço as dúvidas.