ASP.NET Web API 2’deki Birim Testi Denetleyicileri

Bu konuda, Web API 2'deki birim testi denetleyicilerine yönelik bazı özel teknikler açıklanmaktadır. Bu konuyu okumadan önce, çözümünüzde birim testi projesi eklemeyi gösteren Web API 2 ASP.NET Birim Testi öğreticisini okumak isteyebilirsiniz.

Öğreticide kullanılan yazılım sürümleri

Not

Moq kullandım ama aynı fikir sahte çerçeveler için de geçerli. Moq 4.5.30 (ve üzeri), Visual Studio 2017, Roslyn ve .NET 4.5 ve sonraki sürümleri destekler.

Birim testlerinde yaygın bir desen "düzenleme-eylem-onayı"dır:

  • Düzenleme: Testin çalışması için tüm önkoşulları ayarlayın.
  • Eylem: Testi gerçekleştirin.
  • Onay: Testin başarılı olduğunu doğrulayın.

Düzenleme adımında genellikle sahte veya saplama nesneleri kullanırsınız. Bu, bağımlılık sayısını en aza indirir, bu nedenle test tek bir şeyi test etme odaklıdır.

Web API denetleyicilerinizde birim testi için gereken bazı şeyler şunlardır:

  • Eylem doğru yanıt türünü döndürür.
  • Geçersiz parametreler doğru hata yanıtını döndürür.
  • Eylem, depo veya hizmet katmanında doğru yöntemi çağırır.
  • Yanıt bir etki alanı modeli içeriyorsa model türünü doğrulayın.

Bunlar test edilmesi gereken genel şeylerden bazılarıdır, ancak ayrıntılar denetleyici uygulamanıza bağlıdır. Özellikle, denetleyici eylemlerinizin HttpResponseMessage veya IHttpActionResult döndürmesi büyük bir fark yaratır. Bu sonuç türleri hakkında daha fazla bilgi için bkz. Web Api 2'de Eylem Sonuçları.

HttpResponseMessage Döndüren Test Eylemleri

Burada eylemleri HttpResponseMessage döndüren bir denetleyici örneği verilmiştir.

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 IProductRepositoryeklemek için bağımlılık ekleme kullandığına dikkat edin. Bu, denetleyiciyi daha test edilebilir hale getirir, çünkü sahte bir depo ekleyebilirsiniz. Aşağıdaki birim testi, yönteminin Get yanıt gövdesine bir Product yazdığını doğrular. Bunun bir sahte IProductRepositoryolduğunu repository varsayalım.

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

Denetleyicide İstek ve Yapılandırma'yı ayarlamak önemlidir. Aksi takdirde, test ArgumentNullException veya InvalidOperationException ile başarısız olur.

yöntemi yanıtta Post bağlantılar oluşturmak için UrlHelper.Link çağırır. Bu, birim testinde biraz daha kurulum gerektirir:

[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ı istek URL'sine ve yol verilerine ihtiyaç duyar, bu nedenle testin bunlar için değerler ayarlaması gerekir. Bir diğer seçenek de sahte veya saplama UrlHelper'dır. Bu yaklaşımla , ApiController.Url'nin varsayılan değerini sabit değer döndüren bir sahte veya saplama sürümüyle değiştirirsiniz.

Şimdi Moq çerçevesini kullanarak testi yeniden yazalım. Test projesine Moq NuGet paketini yükleyin.

[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, sahte UrlHelper sabit bir dize döndürdüğünden herhangi bir yol verisi ayarlamanız gerekmez.

IHttpActionResult Döndüren Test Eylemleri

Web API 2'de bir denetleyici eylemi, ASP.NET MVC'de ActionResult'a benzer olan IHttpActionResult döndürebilir. IHttpActionResult arabirimi, HTTP yanıtları oluşturmak için bir komut düzeni tanımlar. Denetleyici, yanıtı doğrudan oluşturmak yerine bir IHttpActionResult döndürür. Daha sonra işlem hattı, yanıtı oluşturmak için IHttpActionResult'u çağırır. HttpResponseMessage için gereken kurulumun çoğunu atlayabileceğiniz için bu yaklaşım birim testleri yazmayı kolaylaştırır.

Eylemleri IHttpActionResult döndüren örnek bir denetleyici aşağıda verilmiştir.

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 IHttpActionResult kullanan bazı yaygın desenler gösterilmektedir. Şimdi birim testinin nasıl yapılacağını görelim.

Eylem, yanıt gövdesiyle 200 (Tamam) döndürür

Yöntem, Get ürün bulunursa öğesini çağırır Ok(product) . Birim testinde dönüş türünün OkNegotiatedContentResult olduğundan ve döndürülen ürünün doğru kimliğe sahip olduğundan emin olun.

[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ğini göreceksiniz. Eylem sonucunun HTTP yanıtını doğru oluşturduğunu varsayabilirsiniz. (Bu nedenle Web API çerçevesinin kendi birim testleri vardır!)

Eylem 404 değerini döndürür (Bulunamadı)

Yöntem, Get ürün bulunamazsa çağırır NotFound() . Bu durumda, birim testi yalnızca dönüş türünün NotFoundResult olup olmadığını denetler.

[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 olmadan 200 (Tamam) döndürür

Delete yöntemi boş bir HTTP 200 yanıtı döndürmek için öğesini çağırırOk(). Önceki örnekte olduğu gibi birim testi de dönüş türünü (bu örnekte OkResult) denetler.

[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 içeren 201 (Oluşturuldu) değerini döndürür

yöntemi, Post Konum üst bilgisinde URI'ye sahip bir HTTP 201 yanıtı döndürmek için çağırır CreatedAtRoute . Birim testinde eylemin doğru yönlendirme değerlerini ayarlandığını doğrulayın.

[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övdesine sahip başka bir 2xx döndürür

yöntemi, Put yanıt gövdesiyle bir HTTP 202 (Kabul Edildi) yanıtı döndürmek için çağırır Content . Bu durum 200 (Tamam) döndürmesine benzer ancak birim testinin durum kodunu da denetlemesi gerekir.

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