Minimal APIs in .NET 6 but where are the Unit Tests?
Minimal APIs in .NET 6 is great. But where are the Unit Tests?! Often testing is missed or forgotten because it's perceived as difficult or complex.
- Exploring a minimal Web API with ASP.NET Core 6
- A .NET 6 Minimal API Todo example Playground
- Exploring a minimal WebAPI with ASP.NET Core
- Easier functional and integration testing of ASP.NET Core applications
- Automatic Unit Testing in .NET Core plus Code Coverage in Visual Studio Code
But it's super fun and very easy! Once tests are easy to write, WRITE A LOT OF THEM.
Here's a simple Unit Test of a Web API:
[Fact] public async Task GetTodos() { await using var application = new TodoApplication(); var client = application.CreateClient(); var todos = await client.GetFromJsonAsync<List<Todo>>( "/todos" ); Assert.Empty(todos); } |
Look how nice that is. Client and Server (Application) are right there, and the HTTP GET is just a function call (as this is a Unit Test, not an integration test that covers end-to-end full stack).
Here's the TodoApplication application factory that creates a Host with a mocked out in memory version of a SQLite database.
class TodoApplication : WebApplicationFactory<Todo> { protected override IHost CreateHost(IHostBuilder builder) { var root = new InMemoryDatabaseRoot(); builder.ConfigureServices(services => { services.AddScoped(sp => { // Replace SQLite with the in memory provider for tests return new DbContextOptionsBuilder<TodoDbContext>() .UseInMemoryDatabase( "Tests" , root) .UseApplicationServiceProvider(sp) .Options; }); }); return base .CreateHost(builder); } } |
Nice and clean. You're talking directly to the API, testing just the Unit of Work. No need for HTTP, you're just calling a clean method on the existing API, directly.
That's a simple example, just getting Todos. How would we test making one (POSTing to our Todo application as a Minimal .NET 6 API?)
[Fact] public async Task PostTodos() { await using var application = new TodoApplication(); var client = application.CreateClient(); var response = await client.PostAsJsonAsync( "/todos" , new Todo { Title = "I want to do this thing tomorrow" }); Assert.Equal(HttpStatusCode.Created, response.StatusCode); var todos = await client.GetFromJsonAsync<List<Todo>>( "/todos" ); Assert.Single(todos); Assert.Equal( "I want to do this thing tomorrow" , todos[0].Title); Assert.False(todos[0].IsComplete); } |
You could abstract the setup away if you wanted to and start with an Server/App and Client ready to go, but it's just two lines.
Here we are asserting that it returned an HTTP 200 - even though the HTTP networking stack isn't involved we are still able to test intent. Then we confirm that we created a Todo and could successfully retrieve it from the (in-memory) database.
Pretty slick!
Sponsor: YugabyteDB is a distributed SQL database designed for resilience and scale. It is 100% open source, PostgreSQL-compatible, enterprise-grade, and runs across all clouds. Sign up and get a free t-shirt.
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
About Newsletter
Comments are closed.