.NET Core Design Patterns: Settings and Testing
.NET CLI tooling is king
The new world allows us to build our REST APIs and Web applications in such a way that they are simply executables which you can almost double click on and they will run. The CLI Tooling certainly allows this to a degree:
ps> dotnet RestApi.dll
That's pretty close to running just an .exe.
But if you design your APIs and Web apps like they used to be in the old world, where they depend on a bunch of configuration files, databases and external APIs and things to be able to run, you won't get any value out of this feature. Here's how to maximize your productivity with the new tooling:
Rely on Smart defaults in settings
You should read your application settings in a way that allows you to not define any settings, but instead they default to something that simply works!
What the above design allows you to do is run your API with just pressing F5 on Visual Studio or even calling "dotnet run", while being able to use the new launchSettings.json file inside the project to define nice defaults for these values. Also, you now have the freedom not to define these values at all! Just make sure that your default values are such that they could never work or downgrade security in production!
Your app should work AS LIGHT AS POSSIBLE when developing it
.NET Core added the Environment.IsDevelopment() check. I cannot state how important it is to use this if you wish to keep your development cycle fast in cases where you intend to run the software a lot of times to check it for correctness.
If your application involves a login screen, why not add a module which circumvents this login with the above check. The only rule to follow here is that if you disable security features that get in the way of a smooth working cycle, make sure to follow this:
What we do is we disable an IP Restriction Middleware, and allow for multiple shortcuts in the authentication process of our REST API when we are developing it by checking Environment.IsDevelopment().
Most importantly, while our binaries are exactly the same in development and production - you could not run the development version of our application in the production environment! There are multiple ways to achieve this goal, I can elaborate on those if anyone is interested - let me know :)
What we're able to do because of this design is press F5 in Visual Studio, and the application immediately opens into the exact view where you're able to test the business logic directly without inputting a single value into any input box. This all happens extremely swiftly!
Meanwhile in production this API (same binaries, different settings) requires all of the possible credentials and checks that you can imagine an API would need in this day and age and if you were to swap this production binary into development mode, it would simply not work against the production database.
Our tests account for the fact that we run a lighter version when debugging
Naturally, when the version of our binaries running locally is more lax about security, we need to be able to run a version of the API locally that resembles production as well. Naturally this is no problem and can be tackled in multiple ways with the above design:
Integration tests
- Make a integration test project that is just a regular class library with a bunch of XUnit tests in it. Dotnet CLI tooling allows to run the tests inside the DLL via just calling dotnet vstest or dotnet test
- Via a XUnit fixture, because our web application under test is just an executable, spin up the executable just for the context of running the tests against it with Process.Start()
- Because our smoke tests are now fully in control of the API process running and the environment variables, it can run ANY version of the API with any type of setting. We mainly run a version of the API inside smoke tests that resemble production.
- Our web app is built in such a way, that when you run it as an executable, it will output it's logs to a console window. It is just a console app after all in it's minimal state. When the integration tests spin up the API, it has full control of where to redirect the STD Out of the process as you can see above, and it can simply redirect each line to XUnits output. As you can see in the above code, we redirect STD Out to Log.Information. Then we also redirect Log.Information to XUnit's own test helpers again (basically Trace). This means that the output we see in the Test Explorer window for a single test is exactly what the API logged when the test called it! Pretty cool!
Launchsettings.json allows us to run the WEB APP itself in any version
We're able to run the api in a container like above but also running it like it was running in production is just as easy via Visual Studio:
Because launchSettings.json allows also for setting Environment variables, we are able to make multiple profiles that are different based on what version of the API you wish to emulate. Simply add profiles and pick the version you wish to run from the Visual Studio dropdown menu:
Good times :)