ASP.NET Web API 2’deki Birim Testi DenetleyicileriUnit Testing Controllers in ASP.NET Web API 2

, Mike te sonby Mike Wasson

Bu konuda, Web API 2 ' deki birim testi denetleyicileri için bazı özel teknikler açıklanmaktadır.This topic describes some specific techniques for unit testing controllers in Web API 2. Bu konuyu okumadan önce, çözümünüze bir birim testi projesinin nasıl ekleneceğini gösteren ASP.NET Web API 2 öğretici birim testiniokumak isteyebilirsiniz.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.

Öğreticide kullanılan yazılım sürümleriSoftware versions used in the tutorial

Note

Moq kullandım, ancak aynı fikir herhangi bir sahte işlem çerçevesi için de geçerlidir.I used Moq, but the same idea applies to any mocking framework. Moq 4.5.30 (ve üzeri), Visual Studio 2017, Roslyn ve .NET 4,5 ve sonraki sürümlerini destekler.Moq 4.5.30 (and later) supports Visual Studio 2017, Roslyn and .NET 4.5 and later versions.

Birim testlerinde ortak bir model, " düzenleme-işlem onayı " :A common pattern in unit tests is "arrange-act-assert":

  • Düzenle: testin çalıştırılacağı önkoşulları ayarlayın.Arrange: Set up any prerequisites for the test to run.
  • Davran: testi gerçekleştirin.Act: Perform the test.
  • Onaylama: testin başarılı olduğunu doğrulayın.Assert: Verify that the test succeeded.

Düzenleme adımında, genellikle sahte veya saplama nesneleri kullanacaksınız.In the arrange step, you will often use mock or stub objects. Bu, bağımlılıkların sayısını en aza indirir, böylece test bir şeyi test etmeye odaklanır.That minimizes the number of dependencies, so the test is focused on testing one thing.

Web API denetleyicilerinizde birim testi yapmanız gereken bazı şeyler aşağıda verilmiştir:Here are some things that you should unit test in your Web API controllers:

  • Eylem doğru yanıt türünü döndürür.The action returns the correct type of response.
  • Geçersiz parametreler doğru hata yanıtını döndürür.Invalid parameters return the correct error response.
  • Eylem, depoda veya hizmet katmanında doğru yöntemi çağırır.The action calls the correct method on the repository or service layer.
  • Yanıt bir etki alanı modeli içeriyorsa, model türünü doğrulayın.If the response includes a domain model, verify the model type.

Bunlar, test etmek için bazı genel şeylerdir, ancak Ayrıntılar denetleyici uygulamanıza bağlıdır.These are some of the general things to test, but the specifics depend on your controller implementation. Özellikle, denetleyici eylemlerinizin HttpResponseMessage veya ıhttpactionresultdöndürme konusunda büyük bir farklılık yapar.In particular, it makes a big difference whether your controller actions return HttpResponseMessage or IHttpActionResult. Bu sonuç türleri hakkında daha fazla bilgi için bkz. Web API 2 ' de eylem sonuçları.For more information about these result types, see Action Results in Web Api 2.

HttpResponseMessage döndüren eylemleri test etmeTesting Actions that Return HttpResponseMessage

Eylemleri HttpResponseMessagedöndüren bir denetleyiciye örnek aşağıda verilmiştir.Here is an example of a controller whose actions return HttpResponseMessage.

public class ProductsController : ApiController
{
    IProductRepository _repository;

    public ProductsController(IProductRepository repository)
    {
        _repository = repository;
    }

    public HttpResponseMessage Get(int id)
    {
        Product product = _repository.GetById(id);
        if (product == null)
        {
            return Request.CreateResponse(HttpStatusCode.NotFound);
        }
        return Request.CreateResponse(product);
    }

    public HttpResponseMessage Post(Product product)
    {
        _repository.Add(product);

        var response = Request.CreateResponse(HttpStatusCode.Created, product);
        string uri = Url.Link("DefaultApi", new { id = product.Id });
        response.Headers.Location = new Uri(uri);

        return response;
    }
}

Denetleyicinin bir eklemek için bağımlılık ekleme işlemini kullandığını unutmayın IProductRepository .Notice the controller uses dependency injection to inject an IProductRepository. Bu, bir sahte depo ekleyebildiğinden denetleyiciyi daha kararlı hale getirir.That makes the controller more testable, because you can inject a mock repository. Aşağıdaki birim testi, Get yönteminin yanıt gövdesine yazdığını doğrular Product .The following unit test verifies that the Get method writes a Product to the response body. repositoryBir sahte olduğunu varsayın IProductRepository .Assume that repository is a mock IProductRepository.

[TestMethod]
public void GetReturnsProduct()
{
    // Arrange
    var controller = new ProductsController(repository);
    controller.Request = new HttpRequestMessage();
    controller.Configuration = new HttpConfiguration();

    // Act
    var response = controller.Get(10);

    // Assert
    Product product;
    Assert.IsTrue(response.TryGetContentValue<Product>(out product));
    Assert.AreEqual(10, product.Id);
}

Denetleyici üzerinde istek ve yapılandırma ayarlamak önemlidir.It's important to set Request and Configuration on the controller. Aksi takdirde, test bir ArgumentNullException veya InvalidOperationExceptionile başarısız olur.Otherwise, the test will fail with an ArgumentNullException or InvalidOperationException.

PostYöntemi, yanıtta bağlantı oluşturmak Için UrlHelper. Link öğesini çağırır.The Post method calls UrlHelper.Link to create links in the response. Bu, birim testinde biraz daha fazla kurulum gerektirir:This requires a little more setup in the unit test:

[TestMethod]
public void PostSetsLocationHeader()
{
    // Arrange
    ProductsController controller = new ProductsController(repository);

    controller.Request = new HttpRequestMessage { 
        RequestUri = new Uri("http://localhost/api/products") 
    };
    controller.Configuration = new HttpConfiguration();
    controller.Configuration.Routes.MapHttpRoute(
        name: "DefaultApi", 
        routeTemplate: "api/{controller}/{id}",
        defaults: new { id = RouteParameter.Optional });

    controller.RequestContext.RouteData = new HttpRouteData(
        route: new HttpRoute(),
        values: new HttpRouteValueDictionary { { "controller", "products" } });

    // Act
    Product product = new Product() { Id = 42, Name = "Product1" };
    var response = controller.Post(product);

    // Assert
    Assert.AreEqual("http://localhost/api/products/42", response.Headers.Location.AbsoluteUri);
}

UrlHelper sınıfı için istek URL 'si ve rota verileri gerekir, bu nedenle testin bu değerler için değerleri ayarlaması gerekir.The UrlHelper class needs the request URL and route data, so the test has to set values for these. Diğer bir seçenek de sahte veya saplama UrlHelper.Another option is mock or stub UrlHelper. Bu yaklaşımda, Apicontroller. URL ' nin varsayılan değerini sabit bir değer döndüren bir sahte veya saplama sürümüyle değiştirirsiniz.With this approach, you replace the default value of ApiController.Url with a mock or stub version that returns a fixed value.

Moq çerçevesini kullanarak testi yeniden yazalım.Let's rewrite the test using the Moq framework. MoqNuGet paketini test projesine yükler.Install the Moq NuGet package in the test project.

[TestMethod]
public void PostSetsLocationHeader_MockVersion()
{
    // This version uses a mock UrlHelper.

    // Arrange
    ProductsController controller = new ProductsController(repository);
    controller.Request = new HttpRequestMessage();
    controller.Configuration = new HttpConfiguration();

    string locationUrl = "http://location/";

    // Create the mock and set up the Link method, which is used to create the Location header.
    // The mock version returns a fixed string.
    var mockUrlHelper = new Mock<UrlHelper>();
    mockUrlHelper.Setup(x => x.Link(It.IsAny<string>(), It.IsAny<object>())).Returns(locationUrl);
    controller.Url = mockUrlHelper.Object;

    // Act
    Product product = new Product() { Id = 42 };
    var response = controller.Post(product);

    // Assert
    Assert.AreEqual(locationUrl, response.Headers.Location.AbsoluteUri);
}

Bu sürümde, bir yol verisi ayarlamanız gerekmez, çünkü sahte UrlHelper sabit bir dize döndürür.In this version, you don't need to set up any route data, because the mock UrlHelper returns a constant string.

Ihttpactionresult döndüren test eylemleriTesting Actions that Return IHttpActionResult

Web API 2 ' de, bir denetleyici eylemi ıhttpactionresultdöndürebilir ve bu, ASP.NET MVC 'de ActionResult öğesine benzer.In Web API 2, a controller action can return IHttpActionResult, which is analogous to ActionResult in ASP.NET MVC. Ihttpactionresult ARABIRIMI, http yanıtları oluşturmak için bir komut kalıbı tanımlar.The IHttpActionResult interface defines a command pattern for creating HTTP responses. Yanıt doğrudan oluşturmak yerine, denetleyici bir ıhttpactionresultdöndürür.Instead of creating the response directly, the controller returns an IHttpActionResult. Daha sonra, işlem hattı yanıtı oluşturmak için ıhttpactionresult öğesini çağırır.Later, the pipeline invokes the IHttpActionResult to create the response. Bu yaklaşım, HttpResponseMessageiçin gereken birçok ayarı atlayabilmeniz için birim testlerini yazmayı kolaylaştırır.This approach makes it easier to write unit tests, because you can skip a lot of the setup that is needed for HttpResponseMessage.

Eylemleri ıhttpactionresultdöndüren örnek bir denetleyici aşağıda verilmiştir.Here is an example controller whose actions return IHttpActionResult.

public class Products2Controller : ApiController
{
    IProductRepository _repository;

    public Products2Controller(IProductRepository repository)
    {
        _repository = repository;
    }

    public IHttpActionResult Get(int id)
    {
        Product product = _repository.GetById(id);
        if (product == null)
        {
            return NotFound();
        }
        return Ok(product);
    }

    public IHttpActionResult Post(Product product)
    {
        _repository.Add(product);
        return CreatedAtRoute("DefaultApi", new { id = product.Id }, product);
    }

    public IHttpActionResult Delete(int id)
    {
        _repository.Delete(id);
        return Ok();
    }

    public IHttpActionResult Put(Product product)
    {
        // Do some work (not shown).
        return Content(HttpStatusCode.Accepted, product);
    }    
}

Bu örnekte, ıhttpactionresultkullanılarak bazı yaygın desenler gösterilmektedir.This example shows some common patterns using IHttpActionResult. Ayrıca, birim testinin nasıl test olduğunu görelim.Let's see how to unit test them.

Eylem, yanıt gövdesi ile 200 (Tamam) döndürürAction returns 200 (OK) with a response body

GetYöntemi, Ok(product) ürün bulunursa çağrılır.The Get method calls Ok(product) if the product is found. Birim testinde, dönüş türünün Oknegoti, ContentResult olduğundan ve döndürülen ürünün doğru kimliğe sahip olduğundan emin olun.In the unit test, make sure the return type is OkNegotiatedContentResult and the returned product has the right ID.

[TestMethod]
public void GetReturnsProductWithSameId()
{
    // Arrange
    var mockRepository = new Mock<IProductRepository>();
    mockRepository.Setup(x => x.GetById(42))
        .Returns(new Product { Id = 42 });

    var controller = new Products2Controller(mockRepository.Object);

    // Act
    IHttpActionResult actionResult = controller.Get(42);
    var contentResult = actionResult as OkNegotiatedContentResult<Product>;

    // Assert
    Assert.IsNotNull(contentResult);
    Assert.IsNotNull(contentResult.Content);
    Assert.AreEqual(42, contentResult.Content.Id);
}

Birim testinin eylem sonucunu yürütmediğine dikkat edin.Notice that the unit test doesn't execute the action result. Eylem sonucunun HTTP yanıtını doğru bir şekilde oluşturduğunu varsayabilirsiniz.You can assume the action result creates the HTTP response correctly. (Web API çerçevesinin kendi birim testlerine neden vardır!)(That's why the Web API framework has its own unit tests!)

Eylem 404 döndürüyor (bulunamadı)Action returns 404 (Not Found)

Get NotFound() Ürün bulunamazsa yöntemi çağırır.The Get method calls NotFound() if the product is not found. Bu durumda, birim testi yalnızca dönüş türünün Notfoundsonucuolup olmadığını denetler.For this case, the unit test just checks if the return type is NotFoundResult.

[TestMethod]
public void GetReturnsNotFound()
{
    // Arrange
    var mockRepository = new Mock<IProductRepository>();
    var controller = new Products2Controller(mockRepository.Object);

    // Act
    IHttpActionResult actionResult = controller.Get(10);

    // Assert
    Assert.IsInstanceOfType(actionResult, typeof(NotFoundResult));
}

Eylem, yanıt gövdesi olmayan 200 (Tamam) döndürürAction returns 200 (OK) with no response body

DeleteYöntemi, Ok() boş bir http 200 yanıtı döndürmek için çağırır.The Delete method calls Ok() to return an empty HTTP 200 response. Önceki örnekte olduğu gibi, birim testi dönüş türünü denetler, bu durumda Okresult.Like the previous example, the unit test checks the return type, in this case OkResult.

[TestMethod]
public void DeleteReturnsOk()
{
    // Arrange
    var mockRepository = new Mock<IProductRepository>();
    var controller = new Products2Controller(mockRepository.Object);

    // Act
    IHttpActionResult actionResult = controller.Delete(10);

    // Assert
    Assert.IsInstanceOfType(actionResult, typeof(OkResult));
}

Eylem, konum üst bilgisi ile 201 (oluşturuldu) döndürüyorAction returns 201 (Created) with a Location header

PostYöntemi, CreatedAtRoute konum ÜSTBILGISINDE bir URI ile http 201 yanıtı döndürmek için çağırır.The Post method calls CreatedAtRoute to return an HTTP 201 response with a URI in the Location header. Birim testinde, eylemin doğru yönlendirme değerlerini ayarladiğini doğrulayın.In the unit test, verify that the action sets the correct routing values.

[TestMethod]
public void PostMethodSetsLocationHeader()
{
    // Arrange
    var mockRepository = new Mock<IProductRepository>();
    var controller = new Products2Controller(mockRepository.Object);

    // Act
    IHttpActionResult actionResult = controller.Post(new Product { Id = 10, Name = "Product1" });
    var createdResult = actionResult as CreatedAtRouteNegotiatedContentResult<Product>;

    // Assert
    Assert.IsNotNull(createdResult);
    Assert.AreEqual("DefaultApi", createdResult.RouteName);
    Assert.AreEqual(10, createdResult.RouteValues["id"]);
}

Eylem, yanıt gövdesi olan bir 2xx döndürürAction returns another 2xx with a response body

PutYöntemi Content bir yanıt GÖVDESI ile http 202 (kabul edildi) yanıtı döndürmek için çağırır.The Put method calls Content to return an HTTP 202 (Accepted) response with a response body. Bu durum 200 (Tamam) döndürmeye benzer, ancak birim testi de durum kodunu denetlemelidir.This case is similar to returning 200 (OK), but the unit test should also check the status code.

[TestMethod]
public void PutReturnsContentResult()
{
    // Arrange
    var mockRepository = new Mock<IProductRepository>();
    var controller = new Products2Controller(mockRepository.Object);

    // Act
    IHttpActionResult actionResult = controller.Put(new Product { Id = 10, Name = "Product" });
    var contentResult = actionResult as NegotiatedContentResult<Product>;

    // Assert
    Assert.IsNotNull(contentResult);
    Assert.AreEqual(HttpStatusCode.Accepted, contentResult.StatusCode);
    Assert.IsNotNull(contentResult.Content);
    Assert.AreEqual(10, contentResult.Content.Id);
}

Ek KaynaklarAdditional Resources