Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Topik ini menjelaskan beberapa teknik khusus untuk pengontrol pengujian unit di Web API 2. Sebelum membaca topik ini, Anda mungkin ingin membaca tutorial Pengujian Unit ASP.NET Web API 2, yang menunjukkan cara menambahkan proyek pengujian unit ke solusi Anda.
Versi perangkat lunak yang digunakan dalam tutorial
- Visual Studio 2017
- WEB API 2
- Moq 4.5.30
Catatan
Saya menggunakan Moq, tetapi ide yang sama berlaku untuk kerangka kerja tiruan apa pun. Moq 4.5.30 (dan yang lebih baru) mendukung Visual Studio 2017, Roslyn, dan .NET 4.5 dan versi yang lebih baru.
Pola umum dalam pengujian unit adalah "arrange-act-assert":
- Susun: Siapkan prasyarat apa pun agar pengujian berjalan.
- Act: Lakukan pengujian.
- Pernyataan: Verifikasi bahwa pengujian berhasil.
Dalam langkah susun, Anda akan sering menggunakan objek tiruan atau stub. Itu meminimalkan jumlah dependensi, sehingga pengujian difokuskan pada pengujian satu hal.
Berikut adalah beberapa hal yang harus Anda lakukan pengujian unit di pengontrol API Web Anda:
- Tindakan mengembalikan jenis respons yang benar.
- Parameter yang tidak valid mengembalikan respons kesalahan yang benar.
- Tindakan memanggil metode yang benar pada repositori atau lapisan layanan.
- Jika respons menyertakan model domain, verifikasi jenis model.
Ini adalah beberapa hal umum untuk diuji, tetapi spesifikasinya tergantung pada implementasi pengontrol Anda. Secara khusus, ini membuat perbedaan besar apakah tindakan pengontrol Anda mengembalikan HttpResponseMessage atau IHttpActionResult. Untuk informasi selengkapnya tentang jenis hasil ini, lihat Hasil Tindakan di Web Api 2.
Tindakan Pengujian yang Mengembalikan HttpResponseMessage
Berikut adalah contoh pengontrol yang tindakannya mengembalikan 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;
}
}
Perhatikan pengontrol menggunakan injeksi dependensi untuk menyuntikkan IProductRepository
. Itu membuat pengontrol lebih dapat diuji, karena Anda dapat menyuntikkan repositori tiruan. Pengujian unit berikut memverifikasi bahwa Get
metode menulis Product
ke isi respons. Asumsikan bahwa repository
adalah tiruan 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);
}
Penting untuk mengatur Permintaan dan Konfigurasi pada pengontrol. Jika tidak, pengujian akan gagal dengan ArgumentNullException atau InvalidOperationException.
Menguji Pembuatan Tautan
Metode memanggil Post
UrlHelper.Link untuk membuat tautan dalam respons. Ini membutuhkan sedikit lebih banyak pengaturan dalam pengujian unit:
[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);
}
Kelas UrlHelper memerlukan URL permintaan dan merutekan data, sehingga pengujian harus mengatur nilai untuk ini. Opsi lain adalah tiruan atau stub UrlHelper. Dengan pendekatan ini, Anda mengganti nilai default ApiController.Url dengan versi tiruan atau stub yang mengembalikan nilai tetap.
Mari kita tulis ulang tes menggunakan kerangka kerja Moq .
Moq
Instal paket NuGet dalam proyek pengujian.
[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);
}
Dalam versi ini, Anda tidak perlu menyiapkan data rute apa pun, karena UrlHelper tiruan mengembalikan string konstanta.
Tindakan Pengujian yang Mengembalikan IHttpActionResult
Di Web API 2, tindakan pengontrol dapat mengembalikan IHttpActionResult, yang dianalogikan dengan ActionResult di ASP.NET MVC. Antarmuka IHttpActionResult menentukan pola perintah untuk membuat respons HTTP. Alih-alih membuat respons secara langsung, pengontrol mengembalikan IHttpActionResult. Kemudian, alur memanggil IHttpActionResult untuk membuat respons. Pendekatan ini memudahkan untuk menulis pengujian unit, karena Anda dapat melewati banyak pengaturan yang diperlukan untuk HttpResponseMessage.
Berikut adalah contoh pengontrol yang tindakannya mengembalikan 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);
}
}
Contoh ini memperlihatkan beberapa pola umum menggunakan IHttpActionResult. Mari kita lihat cara unit mengujinya.
Tindakan mengembalikan 200 (OK) dengan isi respons
Metode ini Get
memanggil Ok(product)
jika produk ditemukan. Dalam pengujian unit, pastikan jenis pengembalian adalah OkNegotiatedContentResult dan produk yang dikembalikan memiliki ID yang tepat.
[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);
}
Perhatikan bahwa pengujian unit tidak menjalankan hasil tindakan. Anda dapat mengasumsikan hasil tindakan membuat respons HTTP dengan benar. (Itulah sebabnya kerangka kerja API Web memiliki pengujian unit sendiri!)
Tindakan mengembalikan 404 (Tidak Ditemukan)
Metode Get
memanggil NotFound()
jika produk tidak ditemukan. Untuk kasus ini, pengujian unit hanya memeriksa apakah jenis pengembaliannya adalah 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));
}
Tindakan mengembalikan 200 (OK) tanpa isi respons
Metode ini Delete
memanggil Ok()
untuk mengembalikan respons HTTP 200 kosong. Seperti contoh sebelumnya, pengujian unit memeriksa jenis pengembalian, dalam hal ini 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));
}
Tindakan mengembalikan 201 (Dibuat) dengan header Lokasi
Metode memanggil Post
CreatedAtRoute
untuk mengembalikan respons HTTP 201 dengan URI di header Lokasi. Dalam pengujian unit, verifikasi bahwa tindakan menetapkan nilai perutean yang benar.
[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"]);
}
Tindakan mengembalikan 2xx lain dengan isi respons
Metode ini Put
memanggil Content
untuk mengembalikan respons HTTP 202 (Diterima) dengan isi respons. Kasus ini mirip dengan mengembalikan 200 (OK), tetapi pengujian unit juga harus memeriksa kode status.
[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);
}
Sumber Daya Tambahan
- Kerangka Kerja Entitas Tiruan saat Pengujian Unit ASP.NET Web API 2
- Menulis tes untuk layanan ASP.NET Web API (posting blog oleh Youssef Moussaoui).
- Debugging ASP.NET Web API dengan Route Debugger