Reputation: 13
this is my first time writing unit test, and I just have a few questions. Im using in memory database to test my services and I'am wondering if I'm doing it correctly. My first question is do I need multiple assert on all of my service call? like do i need assert for InsertProduct
? Second, Am I over testing this for using new instance of context on every service call?
[Fact]
public void ProductService_DeleteProduct_Test()
{
// arrange
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseInMemoryDatabase(databaseName: "ProductService_DeleteProduct_Test")
.Options;
var product = new Product() { Id = Guid.NewGuid(), Name = "Product"};
// act
// insert
using (var context = new ApplicationDbContext(options))
{
var service = new Service(context);
service.ProductService.InsertProduct(product);
}
// delete
using (var context = new ApplicationDbContext(options))
{
var service = new Service(context);
service.ProductService.DeleteProducts(new List<Guid> { product.Id });
}
// assert
using (var context = new ApplicationDbContext(options))
{
var service = new Service(context);
Assert.Equal(0, service.ProductService.GetAllProducts().Count);
}
}
Upvotes: 0
Views: 1392
Reputation: 331
Answering your first question, I'd say no. Since this is a unit test and you're testing the delete specifically. I consider the insert part of the setup since you're getting the system into a state where you want to test the delete. To Zoran Horvat's point, if you can, put a row in the database via some means other than the service itself.
To answer your second question, it seems unnecessary to have three using blocks where you're new'ing up the service three times. I'd put the insert, delete, and assert in the same using, making use of one instance of the SUT, or service.
However, if you have multiple tests that all require there to be a row in the database, consider moving the insert into a SetUp method with the [SetUp] attribute that every test can call before hand. In that case, you'd be using multiple instances of the context.
Upvotes: 1
Reputation: 11301
I would make an objection on the structure of your test. Namely, you are using the service (production code) to prepare the underlying database. And you are also using the production code to make the assertion.
If any part of the production code is incorrect, this test will fail. However, this test is designed to assert that the delete feature is doing right.
Therefore, I would rewrite entire test in the following way:
[Fact]
public void ProductService_DeleteProduct_Test()
{
// arrange
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseInMemoryDatabase(databaseName: "ProductService_DeleteProduct_Test")
.Options;
var product = new Product() { Id = Guid.NewGuid(), Name = "Product"};
// Insert object using other means, i.e. direct INSERT statement
// act
using (var context = new ApplicationDbContext(options))
{
var service = new Service(context);
service.ProductService.DeleteProducts(new List<Guid> { product.Id });
}
// assert
// Execute SELECT COUNT(*) instruction to fetch previously existing row
Assert.Equal(0, rowsCount);
}
In this way, you will only touch production code in the acting part of the test. That is the part in which you are using the service object to delete an object from the database.
Subsequent assertion is done against a scalar value count
which is fetched as the result of a raw SELECT
statement executed directly on the storage.
Bottom line is that none of the parts of your test are now depending on correctness of the production code, except the DeleteProducts
method which is in fact the method under test.
And, consequently, the answer to your question is that there is only one assertion to write in this test.
Upvotes: 1