Search code examples
c#unit-testingxunitin-memory-database

Do I need multiple assert? xUnit Test


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

Solution

  • 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.