ในบทความนี้ จะนำเสนอการเขียน unit test เพื่อทดสอบการทำงานของ method ที่ใช้งาน Entity Framework Core ซึ่งตัว Entity Framework Core มาพร้อมความสามารถที่สามารถใช้งาน in-memory store ซึ่งสามารถใช้ในการทดสอบได้ หรือจะใช้ mock framework ในการทดสอบก็ได้ โดยในบทความนี้จะแสดงให้เห็นทั้งสองแนวทาง
In Memory Database
In Memory Database เป็นการสร้าง database จำลองขึ้นมาใช้บน memory แทนที่การใช้ database ตัวจริง ทำให้เราสามาถเขียน unit test โดยที่ไม่ไปกระทบกับ database จริงๆ โดยการเพิ่ม Microsoft.Entityframework.InMemory NuGet package เข้าไปใน test projet
ในส่วนของ DbContext class จะต้องมี constructor ที่รับ DbContectOption เป็น parameter
var options = new DbContextOptionsBuilder<MyDbContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString("N")).Options;
var dbContext = new MyDbContext(options);
จากตัวอย่างด้านบน เราใช้ UseInMemoryDatabase extension method -ของ DbContextOptionsBuilder ซึ่งรับ parameter 1 ตัวเพื่อกำหนดชื่อของ database โดยเราจะใช้ Guid จากนั้นก็สามารถเขียน unit test ได้ดังนี้
[TestClass]
public class MyRepositoryTest
{
private MyDbContext CreateDbContext()
{
var options = new DbContextOptionsBuilder<MyDbContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString("N")).Options;
var dbContext = new MyDbContext(options);
return dbContext;
}
[TestMethod]
public void It_should_add_a_product_successfully()
{
//Arrange
var dbContext = CreateDbContext();
var rep = new MyRepository(dbContext);
var product = new Product
{
ProductName="Toyota Altis",
UnitPrice=900000
};
//Act
var result = rep.Add(product);
//Assert
Assert.IsTrue(result.ProductID > 0);
//Clean up
dbContext.Dispose();
}
[TestMethod]
public void It_should_update_a_product_successfully()
{
//Arrange
var expected = 950000;
var dbContext = CreateDbContext();
var rep = new MyRepository(dbContext);
var product = new Product
{
ProductName="Toyota Altis",
UnitPrice=900000
};
//Act
var result = rep.Add(product);
result.UnitPrice = expected;
rep.Update(result);
result = dbContext.Products.First(p=>p.ProductID == result.ProductID);
//Assert
Assert.AreEqual(expected,result.UnitPrice);
//Clean up
dbContext.Dispose();
}
[TestMethod]
public void It_should_remove_a_product_successfully()
{
//Arrange
var dbContext = CreateDbContext();
var rep = new MyRepository(dbContext);
var product = new Product
{
ProductName="Toyota Altis",
UnitPrice=900000
};
//Act
var result = rep.Add(product);
rep.Delete(result.ProductID);
var isExists = dbContext.Products.Any(p => p.ProductID == result.ProductID);
//Assert
Assert.IsFalse(isExists);
//Clean up
dbContext.Dispose();
}
}
จากตัวอย่างด้านบน CreateDbContext method ทำหน้าที่ในการสร้าง DbContext object ที่ทำงานกับ in-memory database แทนที่จะใช้งาน database ตัวจริง หลังจากนั้นก็สามารถทำตามที่ต้องการได้ เหมือนกับการใช้งาน DbContext ปกติ
Using Mock Frameworks
อีกทางเลือกหนึ่ง เราสามารถใช้ mock frameworks ในการเขียน unit tests ซึ่งเราจะใช้ Moq mock frameworks ซึ่งเป็น mock frameworks ที่ได้รับความนิยมสำหรับ .Net platform โดยก่อนที่จะเริ่มเขียน unit test เราจะต้องกำหนดให้ DbSet pproperty ของ DbContext เป็น virtual ซึงจะทำให้ Moq framework สามารถสร้าง fake object และ override property นี้ได้ และดำเนินการดังนี้
- สร้าง generic collection สำหรับ DbSet property แต่ละตัว
- Config fake DbSet ใน mock framework
- Setup และ forward DbSet property เหล่านั้นไปที่ generic collection
- Provider
- Expression
- ElementType
- GetEnumerator()
[TestClass]
public class MyRepositoryTest
{
private DbSet<T> CreateDbSet<T>(IQueryable<T> collection) where T:class
{
var stubDbSet = new Mock<DbSet<T>>();
stubDbSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(collection.Provider);
stubDbSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(collection.Expression);
stubDbSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(collection.ElementType);
stubDbSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(collection.GetEnumerator());
return stubDbSet.Object;
}
[TestMethod]
public void list_all_available_products_from_the_data_store()
{
//Arrange
var expected = new List<Product>
{
new Product {ProductName ="Toyota yaris"},
new Product {ProductName ="Toyota altis"},
new Product {ProductName ="Toyota camry"}
};
var productDbSet = CreateDbSet(expected.AsQueryable());
var stubContext = new Mock<MyDbContext>();
stubContext.Setup(o=>o.Products).Returns(productDbSet);
var rep = new MyRepository(stubContext.Object);
//Act
var actual = (List<Product>)rep.GetAll();
//Assert
CollectionAssert.AreEquivalent(expected, actual);
}
[TestMethod]
public void return_a_product_for_given_product_id()
{
//Arrange
var expected = "Toyota altis";
var expected = new List<Product>
{
new Product {ProductName ="Toyota yaris"},
new Product {ProductName ="Toyota altis"},
new Product {ProductName ="Toyota camry"}
};
var productDbSet = CreateDbSet(collection.AsQueryable());
var stubContext = new Mock<MyDbContext>();
stubContext.Setup(o => o.Products).Returns(productDbSet);
var rep = new MyRepository(stubContext.Object);
//Act
var actual = rep.GetById(2);
//Assert
Assert.AreEqual(expected, actual.ProductName);
}
}
อ้างอิง : https://www.bitiniyan.com/2019/02/02/how-to-write-unit-tests-with-entity-framework-core/