"
ASP.NET (snapshot 2017) Microsoft documentation and samples

Unit Testing Controllers in ASP.NET Web API 2

by Mike Wasson

This topic describes some specific techniques for unit testing controllers in Web API 2. Before reading this topic, you might want to read the tutorial Unit Testing ASP.NET Web API 2, which shows how to add a unit-test project to your solution.

Software versions used in the tutorial

[!NOTE] I used Moq, but the same idea applies to any mocking framework. Moq 4.5.30 (and later) supports Visual Studio 2017, Roslyn and .NET 4.5 and later versions.

A common pattern in unit tests is “arrange-act-assert”:

In the arrange step, you will often use mock or stub objects. That minimizes the number of dependencies, so the test is focused on testing one thing.

Here are some things that you should unit test in your Web API controllers:

These are some of the general things to test, but the specifics depend on your controller implementation. In particular, it makes a big difference whether your controller actions return HttpResponseMessage or IHttpActionResult. For more information about these result types, see Action Results in Web Api 2.

Testing Actions that Return HttpResponseMessage

Here is an example of a controller whose actions return HttpResponseMessage.

[!code-csharpMain]

   1:  public class ProductsController : ApiController
   2:  {
   3:      IProductRepository _repository;
   4:   
   5:      public ProductsController(IProductRepository repository)
   6:      {
   7:          _repository = repository;
   8:      }
   9:   
  10:      public HttpResponseMessage Get(int id)
  11:      {
  12:          Product product = _repository.GetById(id);
  13:          if (product == null)
  14:          {
  15:              return Request.CreateResponse(HttpStatusCode.NotFound);
  16:          }
  17:          return Request.CreateResponse(product);
  18:      }
  19:   
  20:      public HttpResponseMessage Post(Product product)
  21:      {
  22:          _repository.Add(product);
  23:   
  24:          var response = Request.CreateResponse(HttpStatusCode.Created, product);
  25:          string uri = Url.Link("DefaultApi", new { id = product.Id });
  26:          response.Headers.Location = new Uri(uri);
  27:   
  28:          return response;
  29:      }
  30:  }

Notice the controller uses dependency injection to inject an IProductRepository. That makes the controller more testable, because you can inject a mock repository. The following unit test verifies that the Get method writes a Product to the response body. Assume that repository is a mock IProductRepository.

[!code-csharpMain]

   1:  [TestMethod]
   2:  public void GetReturnsProduct()
   3:  {
   4:      // Arrange
   5:      var controller = new ProductsController(repository);
   6:      controller.Request = new HttpRequestMessage();
   7:      controller.Configuration = new HttpConfiguration();
   8:   
   9:      // Act
  10:      var response = controller.Get(10);
  11:   
  12:      // Assert
  13:      Product product;
  14:      Assert.IsTrue(response.TryGetContentValue<Product>(out product));
  15:      Assert.AreEqual(10, product.Id);
  16:  }

It’s important to set Request and Configuration on the controller. Otherwise, the test will fail with an ArgumentNullException or InvalidOperationException.

The Post method calls UrlHelper.Link to create links in the response. This requires a little more setup in the unit test:

[!code-csharpMain]

   1:  [TestMethod]
   2:  public void PostSetsLocationHeader()
   3:  {
   4:      // Arrange
   5:      ProductsController controller = new ProductsController(repository);
   6:   
   7:      controller.Request = new HttpRequestMessage { 
   8:          RequestUri = new Uri("http://localhost/api/products") 
   9:      };
  10:      controller.Configuration = new HttpConfiguration();
  11:      controller.Configuration.Routes.MapHttpRoute(
  12:          name: "DefaultApi", 
  13:          routeTemplate: "api/{controller}/{id}",
  14:          defaults: new { id = RouteParameter.Optional });
  15:   
  16:      controller.RequestContext.RouteData = new HttpRouteData(
  17:          route: new HttpRoute(),
  18:          values: new HttpRouteValueDictionary { { "controller", "products" } });
  19:   
  20:      // Act
  21:      Product product = new Product() { Id = 42, Name = "Product1" };
  22:      var response = controller.Post(product);
  23:   
  24:      // Assert
  25:      Assert.AreEqual("http://localhost/api/products/42", response.Headers.Location.AbsoluteUri);
  26:  }

The UrlHelper class needs the request URL and route data, so the test has to set values for these. Another option is mock or stub UrlHelper. With this approach, you replace the default value of ApiController.Url with a mock or stub version that returns a fixed value.

Let’s rewrite the test using the Moq framework. Install the Moq NuGet package in the test project.

[!code-csharpMain]

   1:  [TestMethod]
   2:  public void PostSetsLocationHeader_MockVersion()
   3:  {
   4:      // This version uses a mock UrlHelper.
   5:   
   6:      // Arrange
   7:      ProductsController controller = new ProductsController(repository);
   8:      controller.Request = new HttpRequestMessage();
   9:      controller.Configuration = new HttpConfiguration();
  10:   
  11:      string locationUrl = "http://location/";
  12:   
  13:      // Create the mock and set up the Link method, which is used to create the Location header.
  14:      // The mock version returns a fixed string.
  15:      var mockUrlHelper = new Mock<UrlHelper>();
  16:      mockUrlHelper.Setup(x => x.Link(It.IsAny<string>(), It.IsAny<object>())).Returns(locationUrl);
  17:      controller.Url = mockUrlHelper.Object;
  18:   
  19:      // Act
  20:      Product product = new Product() { Id = 42 };
  21:      var response = controller.Post(product);
  22:   
  23:      // Assert
  24:      Assert.AreEqual(locationUrl, response.Headers.Location.AbsoluteUri);
  25:  }

In this version, you don’t need to set up any route data, because the mock UrlHelper returns a constant string.

Testing Actions that Return IHttpActionResult

In Web API 2, a controller action can return IHttpActionResult, which is analogous to ActionResult in ASP.NET MVC. The IHttpActionResult interface defines a command pattern for creating HTTP responses. Instead of creating the response directly, the controller returns an IHttpActionResult. Later, the pipeline invokes the IHttpActionResult to create the response. This approach makes it easier to write unit tests, because you can skip a lot of the setup that is needed for HttpResponseMessage.

Here is an example controller whose actions return IHttpActionResult.

[!code-csharpMain]

   1:  public class Products2Controller : ApiController
   2:  {
   3:      IProductRepository _repository;
   4:   
   5:      public Products2Controller(IProductRepository repository)
   6:      {
   7:          _repository = repository;
   8:      }
   9:   
  10:      public IHttpActionResult Get(int id)
  11:      {
  12:          Product product = _repository.GetById(id);
  13:          if (product == null)
  14:          {
  15:              return NotFound();
  16:          }
  17:          return Ok(product);
  18:      }
  19:   
  20:      public IHttpActionResult Post(Product product)
  21:      {
  22:          _repository.Add(product);
  23:          return CreatedAtRoute("DefaultApi", new { id = product.Id }, product);
  24:      }
  25:   
  26:      public IHttpActionResult Delete(int id)
  27:      {
  28:          _repository.Delete(id);
  29:          return Ok();
  30:      }
  31:   
  32:      public IHttpActionResult Put(Product product)
  33:      {
  34:          // Do some work (not shown).
  35:          return Content(HttpStatusCode.Accepted, product);
  36:      }    
  37:  }

This example shows some common patterns using IHttpActionResult. Let’s see how to unit test them.

Action returns 200 (OK) with a response body

The Get method calls Ok(product) if the product is found. In the unit test, make sure the return type is OkNegotiatedContentResult and the returned product has the right ID.

[!code-csharpMain]

   1:  [TestMethod]
   2:  public void GetReturnsProductWithSameId()
   3:  {
   4:      // Arrange
   5:      var mockRepository = new Mock<IProductRepository>();
   6:      mockRepository.Setup(x => x.GetById(42))
   7:          .Returns(new Product { Id = 42 });
   8:   
   9:      var controller = new Products2Controller(mockRepository.Object);
  10:   
  11:      // Act
  12:      IHttpActionResult actionResult = controller.Get(42);
  13:      var contentResult = actionResult as OkNegotiatedContentResult<Product>;
  14:   
  15:      // Assert
  16:      Assert.IsNotNull(contentResult);
  17:      Assert.IsNotNull(contentResult.Content);
  18:      Assert.AreEqual(42, contentResult.Content.Id);
  19:  }

Notice that the unit test doesn’t execute the action result. You can assume the action result creates the HTTP response correctly. (That’s why the Web API framework has its own unit tests!)

Action returns 404 (Not Found)

The Get method calls NotFound() if the product is not found. For this case, the unit test just checks if the return type is NotFoundResult.

[!code-csharpMain]

   1:  [TestMethod]
   2:  public void GetReturnsNotFound()
   3:  {
   4:      // Arrange
   5:      var mockRepository = new Mock<IProductRepository>();
   6:      var controller = new Products2Controller(mockRepository.Object);
   7:   
   8:      // Act
   9:      IHttpActionResult actionResult = controller.Get(10);
  10:   
  11:      // Assert
  12:      Assert.IsInstanceOfType(actionResult, typeof(NotFoundResult));
  13:  }

Action returns 200 (OK) with no response body

The Delete method calls Ok() to return an empty HTTP 200 response. Like the previous example, the unit test checks the return type, in this case OkResult.

[!code-csharpMain]

   1:  [TestMethod]
   2:  public void DeleteReturnsOk()
   3:  {
   4:      // Arrange
   5:      var mockRepository = new Mock<IProductRepository>();
   6:      var controller = new Products2Controller(mockRepository.Object);
   7:   
   8:      // Act
   9:      IHttpActionResult actionResult = controller.Delete(10);
  10:   
  11:      // Assert
  12:      Assert.IsInstanceOfType(actionResult, typeof(OkResult));
  13:  }

Action returns 201 (Created) with a Location header

The Post method calls CreatedAtRoute to return an HTTP 201 response with a URI in the Location header. In the unit test, verify that the action sets the correct routing values.

[!code-csharpMain]

   1:  [TestMethod]
   2:  public void PostMethodSetsLocationHeader()
   3:  {
   4:      // Arrange
   5:      var mockRepository = new Mock<IProductRepository>();
   6:      var controller = new Products2Controller(mockRepository.Object);
   7:   
   8:      // Act
   9:      IHttpActionResult actionResult = controller.Post(new Product { Id = 10, Name = "Product1" });
  10:      var createdResult = actionResult as CreatedAtRouteNegotiatedContentResult<Product>;
  11:   
  12:      // Assert
  13:      Assert.IsNotNull(createdResult);
  14:      Assert.AreEqual("DefaultApi", createdResult.RouteName);
  15:      Assert.AreEqual(10, createdResult.RouteValues["id"]);
  16:  }

Action returns another 2xx with a response body

The Put method calls Content to return an HTTP 202 (Accepted) response with a response body. This case is similar to returning 200 (OK), but the unit test should also check the status code.

[!code-csharpMain]

   1:  [TestMethod]
   2:  public void PutReturnsContentResult()
   3:  {
   4:      // Arrange
   5:      var mockRepository = new Mock<IProductRepository>();
   6:      var controller = new Products2Controller(mockRepository.Object);
   7:   
   8:      // Act
   9:      IHttpActionResult actionResult = controller.Put(new Product { Id = 10, Name = "Product" });
  10:      var contentResult = actionResult as NegotiatedContentResult<Product>;
  11:   
  12:      // Assert
  13:      Assert.IsNotNull(contentResult);
  14:      Assert.AreEqual(HttpStatusCode.Accepted, contentResult.StatusCode);
  15:      Assert.IsNotNull(contentResult.Content);
  16:      Assert.AreEqual(10, contentResult.Content.Id);
  17:  }

Additional Resources



Comments ( )
Link to this page: //www.vb-net.com/AspNet-DocAndSamples-2017/aspnet/web-api/overview/testing-and-debugging/unit-testing-controllers-in-web-api.htm
< THANKS ME>