Hello #dotnet friends!
We've all been there: you have your data access layer, your API handles all CRUD operations and your unit tests use mocks to simulate the interactions between each object. That's the moment you ask yourself: am I really testing anything? Since every object interacts with a mocked, simplified version of its dependencies, we're only validating the very basic functionality of the data manipulation classes.
Implementing integration tests would solve that, right? The issue is that it's not always feasible to touch the database for manipulation. What if you have a mass delete operation or something that will cause all kinds of trouble if you have to run it in production? Your test engine probably won't (and it really shouldn't) have production database access. For that scenario there's a tool that finds a middle ground between the convenience of automation with a safe, flexible and closer-to-real-data approach of a full integration test, which is to virtualize the DB. If you already use EntityFramework Core the whole process can be quite simple!
Let's say you have a database context that looks like this:
public partial class SampleContext : DbContext
{
public SampleContext(DbContextOptions<SampleContext> options)
: base(options) { }
public virtual DbSet<Product> Product { get; set; }
}
Then you probably inject that database context into your services DI provider. It will look something like this:
services.AddDbContext<SampleContext>(options =>options.UseSqlServer(
configuration.GetConnectionString("MyConnectionString")));
This is all very typical code for EFCore and .NETCore web APIs. If you have something similar, you're in luck! I normally prefer xUnit but if you use NUnit or any other testing framework, it should look more or less the same. In your startup phase or constructor you can simply instance the DatabaseContext object in a virtualized way:
public class SampleTests
{
private readonly SampleContext _virtualContext;
public SampleTests()
{
_virtualContext = new SampleContext(
new DbContextOptionsBuilder<SampleContext>()
.UseInMemoryDatabase(databaseName: "SampleDb")
.Options
);
}
}
And it's pretty much it! Now you can add data relevant to your tests and let your test methods go to town, delete, truncate, explode the database that everything will be gone once the execution is complete. Here's a basic data initializer function for the example above (using the same _virtualcontext and the Product record:
List<Product> products = new()
{
new Product()
{
ProductName = "TestProduct"
},
new Product()
{
ProductName = "AnotherTestProduct"
}
};
products.ForEach(p => _virtualContext.Products.Add(p));
_virtualContext.SaveChanges();
Now you can write tests that call the Controllers/Endpoints and validate the outputs, along with the data manipulations. Once the test manager starts up the API, it'll instance the virtualized DB and the underlying code will be agnostic to it. Nothing quite like fooling your own code, right?
Let me know if it's been helpful and follow me on LinkedIn. Thanks for the read!
Top comments (1)
Hmmmm
I didn't know you could do this! Good find
I, too, have trust issues with mock objects. So I'll probably start using this haha