Back to overview

Outside in testing - To mock or not to Stub

By Ben Luts

.NET Methodology

Jul 2025

I’ve been going around conferences talking about it a couple of times now, so why not write a blog post about it. So what is outside in testing? In a nutshell it’s a specific way of writing your tests. Tests that don’t require constant updates when you refactor your code, tests that test what matters, the actual behaviour of your code from the point of view of the caller. We shouldn’t care about the implementation details, these can (and perhaps should) change quite often. Having to constantly update our tests is one of the main reasons why people stop writing them or aren’t allowed to write tests altogether.

How? 

As already stated we test our code from the perspective of the caller. If we provide a class library, we should test from the entry points to our library. When we provide a RESTful API our callers will use HTTP requests, so we test using actual HTTP requests.  

The reasons we write tests is to prove that our code is actually doing what business asked it to do. We write tests to prove to us, as developers, that we have satisfied all requirements and that they actually work. We care about the ins & outs of the code we write. Say we are writing a calculator app and the input is multiply 5 and 3, we don’t really care if its 5 X 3 or 3 X 5 or even 5 + 5 + 5. As long as the result is 15 we have satisfied our business requirement.  

The benefit of this approach is that we put our business requirements first. They become first class citizens in our code base. It also makes our tests very readable, or at least when done right. Every test tells exactly what is going on, what we are testing and what we expect to happen. By employing evidence based testing, we also ensure that every test isn’t full of bootstrapping code either. 

There are some more benefits, but my favourite one has to be the (almost) complete absence of mocks (or stubs). No more risk of having to update all the mocks when making a code change. No more tests that aren’t actually testing anything, except for the correctness of the mocks. We test the entire application holistically, we can remain confident in our code changes. 

Code 

Lets look at some code examples on how we can achieve this. For the sake of keeping the examples simple, I created a simple RESTful API using .NET Web API. I read and write data to a SQL Server Database in a test container using Entity Framework, but you can of course use your preferred database engine and ORM. 

Let’s start by looking at the actual test case, even without knowing how we bootstrapped everything, the use case for the is pretty clear. 

[Fact] 
public async Task AddWeatherForecast_WithValidRequest_ReturnsOk() 
{ 
    var validWeatherForecast = GetValidWeatherForecast(); 
    var addResponse = await _api.PostAsJsonAsync("weatherforecasts", validWeatherForecast); 
    Assert.Equal(HttpStatusCode.OK, addResponse.StatusCode); 
} 

Right away from the test method name we can tell that we are trying to add a weather forecast. When giving a valid one, we expect an OK Response. Since we used evidence based testing, I have absolutely no bootstrapping code in my actual test cases. I do however see that we’re getting a valid forecast and posting that to the API, we then proceed to assert the result. 

We can now add more tests, to make sure we have covered every requirement for our Add endpoint. For brevity, I won’t all add them here, but you get the gist. 

Let’s look at the different parts of bootstrapping that is going on behind the scenes that we are using in this simple test case. Starting with the GetValidWeatherForecast() method. For the purpose of our test we don’t need to know how a valid forecast looks like, but of course we will need to create one somewhere. 

private static object GetValidWeatherForecast() 
{ 
    return new 
    { 
        date = DateOnly.ParseExact("12/06/2025", "dd/MM/yyyy", CultureInfo.GetCultureInfo("nl-BE")), 
        temperatureC = 23, 
        summary = "Beautiful sunny day, ideal motorcycle weather" 
    }; 
} 

In this example it can be a simple method that can get reused when needed. Nothing complicated going on here. 

What’s more interesting is the _api that we are using. For this we take advantage of the WebApplicationFactory available in the Microsoft.AspNetCore.Mvc.Testing NuGet package. This allows us to start our actual API completely in memory. 

public class ApiFactory<Program> : WebApplicationFactory<Program> where Program : class 
{ 
    private readonly string _dbConnectionstring; 
    public ApiFactory(string dbConnectionstring) 
    { 
        _dbConnectionstring = dbConnectionstring; 
    } 
    protected override void ConfigureWebHost(IWebHostBuilder builder) 
    { 
        Environment.SetEnvironmentVariable("ConnectionStrings:DefaultConnection", _dbConnectionstring); 
        builder.ConfigureTestServices(services => 
        { 
            services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options => 
            { 
                var config = new OpenIdConnectConfiguration() 
                { 
                    Issuer = FakeJwtToken.Issuer, 
                }; 

                options.Audience = FakeJwtToken.Audience; 
                config.SigningKeys.Add(FakeJwtToken.SecurityKey); 
                options.Configuration = config; 
            }); 
        }); 
    } 
} 

Due to the nature of the test containers I need to pass in the connection string for the database using the environment variables. So we override the ConfigureWebHost method. The Program we use as the parameter to the WebApplicationFactory is our API Program.cs file. Be careful though, the new .NET style of the program class, means we can’t actually use it like this from the get go, but we have two options. Either convert your Program.cs to a more classis Program class or add the following at the bottom of the file. I know it’s a little ugly, but there’s not really anything we can do about it. 

public partial class Program { } 

As you can see there is a little more going on in the ConfigureWebHost() method, since most API’s (I hope) have some form of authentication / authorization, we might need to make sure that during testing it accepts our self created “fake” JWT Token. This is a better option than to simply disable authentication & authorization for testing, because that’s not how our callers will use it. 

Conclusion 

Setting up our tests using Outside in testing principles can seem a little daunting at first, because we need to do some Bootstrapping, but once it’s correctly setup we can write better tests and create them faster in comparison to needing to setup all the mocks/stubs all the time. The time you loose initially, especially when not familiar with the tools, will fade in comparison to having to update your tests every time you make a code change. 

Resources 

I have a sample app on my GitHub page where you can see how I use my test containers and  do the entire bootstrapping of the test setup. There are examples for both MS Sql Server & PostgreSQL.  
/BenLuts/RippLib.CodeSamples

Decorator Pattern Thumb

By Paul Karam

Mar 2025

Decorator Pattern

One of the biggest challenges in our daily job is keeping our code clean, easy to read, extendable, and understandable. It's a difficult task, but it becomes ...

Strong Under Pressure: resilient HTTP clients Thumb

By Michiel Mijnhardt

Nov 2024

Strong Under Pressure: resilient HTTP clients

Building resilient applications is crucial, and part of that resiliency is making sure your applications outgoing http requests are covered. The .NET go to ...

An introduction to NSwag Thumb

By Karel Verhulst

Aug 2024

An introduction to NSwag

In the world of modern web development, API's play a crucial role in enabling communication between different software systems. The process of creating, ...

Cache primary btn default asset Cache primary btn hover asset Cache white btn default asset Cache white btn hover asset