0

I'm trying to update how the company I work in initialize ASP.NET web apps and web APIs. First of all, because in our older 3.1 .NET Core setup we have no way to access a test server, and have to run integration tests through a socket on localhost, but also to try and keep our code more up to date.

Being blocked by the new magic of minimal APIs I've tried to search for how to NOT use minimal APIs, but haven't found anything that have helped me define my own Program.cs and put the code there in a way that make a test server available for unit tests.

I've used docs to set up a unit test using a test server, but when unit testing, I want to be able to create instances of objects in unit tests and supply everything I want into app init code in Program.cs. I want to be able to specify what command line arguments to use for the test. I want to be able to set a flag that makes the normal app init skip doing something. When I want to supply a mock/stub, I don't want the one running in production to be created first and then replaced.

The .NET 6 minimal code feature looks to me like an anti-feature. Why introduce new parser complexities, magic, automatic class generation and sacrifice (or obfuscate) flexibility with unit testing, just to make hello world applications save a few lines of code?

Some stuff that I have tested:

  • Creating a partial Program.cs implementation doesn't work, as Program.cs can't access those members for some reason.
  • There is no "this." I can use in magic Program.cs parts either. I guess the magic Program.cs exports into static contexts or hide the instance.
  • There is no member in the auto-generated Program class that I've found where I can insert something on creation. I've tried to misuse services to inject stuff, but they are not available prior to "var app = builder.Build();" statement which is too late.
  • If I try to debug and look into implementation of the magic Program class I'm just thrown into my minimal code which hides the details..

A static variable works, but that makes us unable to run multiple unit tests in parallel. I was hoping for a workaround by creating a static dictionary of dictionaries, and be able to send through a string key for the app to use to lookup it's test bits I want to inject, but even just passing that string key I haven't found a way to do yet, and having to implement this to begin with sounds horrible.

Some talk about app config files, but I want to be able to control this in code in unit tests and not in files, which is just a more horrific way of using static bits.

How are unit tests supposed to input a test context as input to Program.cs execution?

Bjarne
  • 81
  • 1
  • 5
  • Does this answer your question? [Integration test and hosting ASP.NET Core 6.0 without Startup class](https://stackoverflow.com/questions/70093628/integration-test-and-hosting-asp-net-core-6-0-without-startup-class) – Guru Stron Aug 16 '22 at 13:51
  • No.. That covers how to set up an integration test with a test server, and I used that or similar resource to do that.. But it doesn't cover how to give your Program.cs code input from the test context. – Bjarne Aug 16 '22 at 15:02

2 Answers2

1

I want to be able to specify what command line arguments to use for the test. I want to be able to set a flag that makes the normal app init skip doing something. When I want to supply a mock/stub, I don't want the one running in production to be created first and then replaced.

There are a couple ways to do this:

  1. Change then environment - You can use the environment to determine what to skip at startup.
  2. Change the configuration - You can set configuration (I see you found that already).
  • You can also change hosting configuration which might do what you want.
  1. Change services - You can override services in unit tests.

It's unclear from the above what you're trying to do exactly though. If you could provide more code (what the test is trying to set and what the app is trying do as a result), it would be helpful.

davidfowl
  • 37,120
  • 7
  • 93
  • 103
0

I've found a possible workaround at least.. By injecting a string through app config, I'm at least able to fetch an id right after "var builder = WebApplication.CreateBuilder(args);" statement. And then use a static class that generates a unique key in test init that I can send along for the app to fetch its own context..

That way I can in my unit test do:

class MyWebApplication : WebApplicationFactory<Program>
{
    internal int ConfigContextId { get; init; }

    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.UseSetting("externalConfigContextId", ConfigContextId.ToString());
        ...
    }
}

[TestClass]
public class WebApiTest
{
    [TestMethod]
    public async Task TestAccessingController()
    {
        var config = new ProgramInitOptions
        {
            AppName = "SampleApp"
        };
        var testContext = StaticObjectDictionary.GetNewContext();
        testContext.SetObject("config", config);
        var application = new MyWebApplication { ConfigContextId = testContext.ContextId };

and in app init do:

var builder = WebApplication.CreateBuilder(args);
var contextId = builder.Configuration["externalConfigContextId"];
var config = (ProgramInitOptions) StaticObjectDictionary.GetContext(int.Parse(contextId)).GetObject("config");

where StaticObjectDictionary is my helper class to create a new unique context per test run in a lock-safe static way, and ProgramInitOptions is my custom class to contain whatever I want as input to the program..

Still, I can't change the arguments sent to WebApplication.CreateBuilder, which is sad, but I don't think we use any arguments that we don't handle explicitly ourselves anyhow, so shouldn't matter..

Would be nice if there was a way to supply the command line arguments to test and avoid the static dictionary thing though.

If there isn't a more elegant fix to this, I don't understand how they managed to make that minimal feature default.

Bjarne
  • 81
  • 1
  • 5