Web API 2 ASP.NET Birim Testi sırasında Sahte Varlık Çerçevesi
yazan: Tom FitzMacken
Bu kılavuz ve uygulama, Entity Framework kullanan Web API 2 uygulamanız için birim testlerinin nasıl oluşturulacağını gösterir. Yapı iskelesi oluşturulmuş denetleyicinin test için bir bağlam nesnesi geçirmeyi etkinleştirmek üzere nasıl değiştirildiğini ve Entity Framework ile çalışan test nesnelerinin nasıl oluşturulacağını gösterir.
ASP.NET Web API'siyle birim testine giriş için bkz. ASP.NET Web API 2 ile Birim Testi.
Bu öğreticide, ASP.NET Web API'sinin temel kavramlarını bildiğiniz varsayılır. Giriş niteliğindeki bir öğretici için bkz. ASP.NET Web API 2 ile Çalışmaya Başlama.
Öğreticide kullanılan yazılım sürümleri
- Visual Studio 2017
- Web API 2
Bu konuda
Bu konu aşağıdaki bölümleri içermektedir:
- Önkoşullar
- Kodu indirin
- Birim testi projesiyle uygulama oluşturma
- Model sınıfını oluşturma
- Denetleyiciyi ekleme
- Bağımlılık ekleme
- Test projesine NuGet paketlerini yükleme
- Test bağlamı oluşturma
- Test oluşturma
- Testleri çalıştırma
ASP.NET Web API 2 ile Birim Testi bölümündeki adımları tamamladıysanız Denetleyiciyi ekleme bölümüne atlayabilirsiniz.
Önkoşullar
Visual Studio 2017 Community, Professional veya Enterprise sürümü
Kodu indirin
Tamamlanan projeyi indirin. İndirilebilir proje, bu konuya ve Birim Testi ASP.NET Web API 2 konusuna yönelik birim testi kodunu içerir.
Birim testi projesiyle uygulama oluşturma
Uygulamanızı oluştururken birim testi projesi oluşturabilir veya mevcut bir uygulamaya birim testi projesi ekleyebilirsiniz. Bu öğreticide, uygulama oluşturulurken birim testi projesi oluşturma gösterilmektedir.
StoreApp adlı yeni bir ASP.NET Web Uygulaması oluşturun.
Yeni ASP.NET Projesi pencerelerinde Boş şablonu seçin ve Web API'sinin klasörlerini ve temel başvurularını ekleyin. Birim testleri ekle seçeneğini belirleyin. Birim testi projesi otomatik olarak StoreApp.Tests olarak adlandırılır. Bu adı kullanabilirsiniz.
Uygulamayı oluşturduktan sonra iki proje içerdiğini göreceksiniz: StoreApp ve StoreApp.Tests.
Model sınıfını oluşturma
StoreApp projenizde , Models klasörüne Product.cs adlı bir sınıf dosyası ekleyin. Dosyanın içeriğini aşağıdaki kodla değiştirin.
using System;
namespace StoreApp.Models
{
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
}
Çözümü derleyin.
Denetleyiciyi ekleme
Denetleyiciler klasörüne sağ tıklayın ve Ekle ve Yeni İskeleli Öğe'yi seçin. Entity Framework kullanarak eylemleri olan Web API 2 Denetleyicisi'ni seçin.
Aşağıdaki değerleri ayarlayın:
- Denetleyici adı: ProductController
- Model sınıfı: Ürün
- Veri bağlamı sınıfı: [Aşağıda görülen değerleri dolduran Yeni veri bağlamı düğmesini seçin]
Denetleyiciyi otomatik olarak oluşturulan kodla oluşturmak için Ekle'ye tıklayın. Kod, Product sınıfının örneklerini oluşturma, alma, güncelleştirme ve silme yöntemlerini içerir. Aşağıdaki kod ürün ekleme yöntemini gösterir. yönteminin bir IHttpActionResult örneği döndürdüğüne dikkat edin.
// POST api/Product
[ResponseType(typeof(Product))]
public IHttpActionResult PostProduct(Product product)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.Products.Add(product);
db.SaveChanges();
return CreatedAtRoute("DefaultApi", new { id = product.Id }, product);
}
IHttpActionResult, Web API 2'deki yeni özelliklerden biridir ve birim testi geliştirmeyi basitleştirir.
Sonraki bölümde, test nesnelerinin denetleyiciye geçirilmesini kolaylaştırmak için oluşturulan kodu özelleştireceğiz.
Bağımlılık ekleme
Şu anda ProductController sınıfı, StoreAppContext sınıfının bir örneğini kullanmak için sabit kodlanmıştır. Uygulamanızı değiştirmek ve bu sabit kodlanmış bağımlılığı kaldırmak için bağımlılık ekleme adlı bir desen kullanacaksınız. Bu bağımlılığı bozarak test ederken sahte bir nesne geçirebilirsiniz.
Models klasörüne sağ tıklayın ve IStoreAppContext adlı yeni bir arabirim ekleyin.
Kodu aşağıdaki kodla değiştirin.
using System;
using System.Data.Entity;
namespace StoreApp.Models
{
public interface IStoreAppContext : IDisposable
{
DbSet<Product> Products { get; }
int SaveChanges();
void MarkAsModified(Product item);
}
}
StoreAppContext.cs dosyasını açın ve aşağıdaki vurgulanmış değişiklikleri yapın. Dikkat edilmesi gereken önemli değişiklikler şunlardır:
- StoreAppContext sınıfı IStoreAppContext arabirimini uygular
- MarkAsModified yöntemi uygulandı
using System;
using System.Data.Entity;
namespace StoreApp.Models
{
public class StoreAppContext : DbContext, IStoreAppContext
{
public StoreAppContext() : base("name=StoreAppContext")
{
}
public DbSet<Product> Products { get; set; }
public void MarkAsModified(Product item)
{
Entry(item).State = EntityState.Modified;
}
}
}
ProductController.cs dosyasını açın. Mevcut kodu vurgulanan kodla eşleşecek şekilde değiştirin. Bu değişiklikler StoreAppContext bağımlılığını bozar ve diğer sınıfların bağlam sınıfı için farklı bir nesne geçirmesini sağlar. Bu değişiklik, birim testleri sırasında test bağlamı geçirmenizi sağlar.
public class ProductController : ApiController
{
// modify the type of the db field
private IStoreAppContext db = new StoreAppContext();
// add these constructors
public ProductController() { }
public ProductController(IStoreAppContext context)
{
db = context;
}
// rest of class not shown
}
ProductController'da yapmanız gereken bir değişiklik daha var. PutProduct yönteminde, varlık durumunu değiştirecek şekilde ayarlayan satırı MarkAsModified yöntemine yapılan bir çağrıyla değiştirin.
// PUT api/Product/5
public IHttpActionResult PutProduct(int id, Product product)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (id != product.Id)
{
return BadRequest();
}
//db.Entry(product).State = EntityState.Modified;
db.MarkAsModified(product);
// rest of method not shown
}
Çözümü derleyin.
Artık test projesini ayarlamaya hazırsınız.
Test projesine NuGet paketlerini yükleme
Uygulama oluşturmak için Boş şablonunu kullandığınızda, birim testi projesi (StoreApp.Tests) yüklü NuGet paketi içermez. Web API şablonu gibi diğer şablonlar, birim testi projesinde bazı NuGet paketlerini içerir. Bu öğretici için Entity Framework paketini ve Microsoft ASP.NET Web API 2 Core paketini test projesine eklemeniz gerekir.
StoreApp.Tests projesine sağ tıklayın ve NuGet Paketlerini Yönet'i seçin. Paketleri bu projeye eklemek için StoreApp.Tests projesini seçmeniz gerekir.
Çevrimiçi paketlerden EntityFramework paketini (sürüm 6.0 veya üzeri) bulup yükleyin. EntityFramework paketinin zaten yüklü olduğu görünüyorsa StoreApp.Tests projesi yerine StoreApp projesini seçmiş olabilirsiniz.
Microsoft ASP.NET Web API 2 Core paketini bulun ve yükleyin.
NuGet Paketlerini Yönet penceresini kapatın.
Test bağlamı oluşturma
Test projesine TestDbSet adlı bir sınıf ekleyin. Bu sınıf, test veri kümeniz için temel sınıf görevi görür. Kodu aşağıdaki kodla değiştirin.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Entity;
using System.Linq;
namespace StoreApp.Tests
{
public class TestDbSet<T> : DbSet<T>, IQueryable, IEnumerable<T>
where T : class
{
ObservableCollection<T> _data;
IQueryable _query;
public TestDbSet()
{
_data = new ObservableCollection<T>();
_query = _data.AsQueryable();
}
public override T Add(T item)
{
_data.Add(item);
return item;
}
public override T Remove(T item)
{
_data.Remove(item);
return item;
}
public override T Attach(T item)
{
_data.Add(item);
return item;
}
public override T Create()
{
return Activator.CreateInstance<T>();
}
public override TDerivedEntity Create<TDerivedEntity>()
{
return Activator.CreateInstance<TDerivedEntity>();
}
public override ObservableCollection<T> Local
{
get { return new ObservableCollection<T>(_data); }
}
Type IQueryable.ElementType
{
get { return _query.ElementType; }
}
System.Linq.Expressions.Expression IQueryable.Expression
{
get { return _query.Expression; }
}
IQueryProvider IQueryable.Provider
{
get { return _query.Provider; }
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return _data.GetEnumerator();
}
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return _data.GetEnumerator();
}
}
}
Aşağıdaki kodu içeren test projesine TestProductDbSet adlı bir sınıf ekleyin.
using System;
using System.Linq;
using StoreApp.Models;
namespace StoreApp.Tests
{
class TestProductDbSet : TestDbSet<Product>
{
public override Product Find(params object[] keyValues)
{
return this.SingleOrDefault(product => product.Id == (int)keyValues.Single());
}
}
}
TestStoreAppContext adlı bir sınıf ekleyin ve mevcut kodu aşağıdaki kodla değiştirin.
using System;
using System.Data.Entity;
using StoreApp.Models;
namespace StoreApp.Tests
{
public class TestStoreAppContext : IStoreAppContext
{
public TestStoreAppContext()
{
this.Products = new TestProductDbSet();
}
public DbSet<Product> Products { get; set; }
public int SaveChanges()
{
return 0;
}
public void MarkAsModified(Product item) { }
public void Dispose() { }
}
}
Test oluşturma
Varsayılan olarak, test projeniz UnitTest1.cs adlı boş bir test dosyası içerir. Bu dosya, test yöntemleri oluşturmak için kullandığınız öznitelikleri gösterir. Bu öğreticide, yeni bir test sınıfı ekleyeceğiniz için bu dosyayı silebilirsiniz.
Test projesine TestProductController adlı bir sınıf ekleyin. Kodu aşağıdaki kodla değiştirin.
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Web.Http.Results;
using System.Net;
using StoreApp.Models;
using StoreApp.Controllers;
namespace StoreApp.Tests
{
[TestClass]
public class TestProductController
{
[TestMethod]
public void PostProduct_ShouldReturnSameProduct()
{
var controller = new ProductController(new TestStoreAppContext());
var item = GetDemoProduct();
var result =
controller.PostProduct(item) as CreatedAtRouteNegotiatedContentResult<Product>;
Assert.IsNotNull(result);
Assert.AreEqual(result.RouteName, "DefaultApi");
Assert.AreEqual(result.RouteValues["id"], result.Content.Id);
Assert.AreEqual(result.Content.Name, item.Name);
}
[TestMethod]
public void PutProduct_ShouldReturnStatusCode()
{
var controller = new ProductController(new TestStoreAppContext());
var item = GetDemoProduct();
var result = controller.PutProduct(item.Id, item) as StatusCodeResult;
Assert.IsNotNull(result);
Assert.IsInstanceOfType(result, typeof(StatusCodeResult));
Assert.AreEqual(HttpStatusCode.NoContent, result.StatusCode);
}
[TestMethod]
public void PutProduct_ShouldFail_WhenDifferentID()
{
var controller = new ProductController(new TestStoreAppContext());
var badresult = controller.PutProduct(999, GetDemoProduct());
Assert.IsInstanceOfType(badresult, typeof(BadRequestResult));
}
[TestMethod]
public void GetProduct_ShouldReturnProductWithSameID()
{
var context = new TestStoreAppContext();
context.Products.Add(GetDemoProduct());
var controller = new ProductController(context);
var result = controller.GetProduct(3) as OkNegotiatedContentResult<Product>;
Assert.IsNotNull(result);
Assert.AreEqual(3, result.Content.Id);
}
[TestMethod]
public void GetProducts_ShouldReturnAllProducts()
{
var context = new TestStoreAppContext();
context.Products.Add(new Product { Id = 1, Name = "Demo1", Price = 20 });
context.Products.Add(new Product { Id = 2, Name = "Demo2", Price = 30 });
context.Products.Add(new Product { Id = 3, Name = "Demo3", Price = 40 });
var controller = new ProductController(context);
var result = controller.GetProducts() as TestProductDbSet;
Assert.IsNotNull(result);
Assert.AreEqual(3, result.Local.Count);
}
[TestMethod]
public void DeleteProduct_ShouldReturnOK()
{
var context = new TestStoreAppContext();
var item = GetDemoProduct();
context.Products.Add(item);
var controller = new ProductController(context);
var result = controller.DeleteProduct(3) as OkNegotiatedContentResult<Product>;
Assert.IsNotNull(result);
Assert.AreEqual(item.Id, result.Content.Id);
}
Product GetDemoProduct()
{
return new Product() { Id = 3, Name = "Demo name", Price = 5 };
}
}
}
Testleri çalıştırma
Artık testleri çalıştırmaya hazırsınız. TestMethod özniteliğiyle işaretlenmiş tüm yöntem test edilir. Test menü öğesinden testleri çalıştırın.
Test Gezgini penceresini açın ve testlerin sonuçlarına dikkat edin.