9

I was fiddling with top-level statements as the entry point for a simple console app, since the new .NET 6 template use them as a default.

Yet, as the language specification very clearly states:

Note that the names "Program" and "Main" are used only for illustrations purposes, actual names used by compiler are implementation dependent and neither the type, nor the method can be referenced by name from source code.

So, if I can't reference the implicit Program class and it's Main() method, would it be possible to write unit tests to check the execution flow of the top-level statements themselves? If so, how?

Silvio Dayube
  • 93
  • 1
  • 4
  • Is there a real need for this? Is this perhaps just a research question? Sort of like, "can I make a pig fly" type of question? (the answer is yes for pigs by the way, just not for long) – Lasse V. Karlsen Jan 09 '22 at 20:36
  • one question: ***why*** would anyone actually want to do this? no program that's complex enough to warrant unit tests has (or: should have) any major functionality in the Main(), or on the top-level. – Franz Gleichmann Jan 09 '22 at 20:39
  • @Lasse A bit of both, actually. I think it's great for new learners to use console apps to improve their skills, and if they happen to be learning about unit tests it may be helpful to be able to test the execution of the compiler generated Main() method. But I have to admit that the urge to know if it's possible is mostly out of curiosity about the limitations of top-level statements. – Silvio Dayube Jan 09 '22 at 20:43
  • @Franz I agree, but I'm still curious to know if there's a way. – Silvio Dayube Jan 09 '22 at 20:45
  • Small, simple projects can also benefit from unit testing. For example even a little 10-line algorithm to "clean up" a CSV text file can apply to a lot more scenarios than you initially expect, so unit tests are a good way to be sure you're getting the new scenes right and not breaking old ones as you make changes. – R.D. Alkire Mar 27 '23 at 21:09

1 Answers1

16

Yes. One option (since .NET 6) is to make the tested project's internals visible to the test project for example by adding next property to csproj:

<ItemGroup>
  <InternalsVisibleTo Include ="YourTestProjectName"/>
</ItemGroup>

And then the Program class generated for top-level statement should be visible to the test project and you can run it next way:

var entryPoint = typeof(Program).Assembly.EntryPoint!;
entryPoint.Invoke(null, new object[] { Array.Empty<string>() }); 

Something like this is used internally to perform integration tests for ASP.NET Core 6 with minimal hosting model.

Note that generated Main method can return task if you are using await's in your top-level statement, so you possibly will need to capture the return of entryPoint.Invoke and test if it is a Task and await it.

Another approach is to explicitly declare Program class as partial (for example at the end of top-level statement and use it in testing project):

// ...
// your top-level statements

public partial class Program { }
Guru Stron
  • 102,774
  • 10
  • 95
  • 132
  • Thanks for the detailed answer! – Silvio Dayube Jan 09 '22 at 23:42
  • Although the name Program is used right now, since the documentation explicitly states it is implementation-dependent, this might break in the future. – Lasse V. Karlsen Jan 10 '22 at 08:44
  • @LasseV.Karlsen yes, though this approach is recommended by Microsoft for [integration testing](https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-6.0#basic-tests-with-the-default-webapplicationfactory). – Guru Stron Jan 10 '22 at 09:58
  • There is a slight difference though. If you explicitly declare `public partial class Program { }`, then even if the type name hosting the entrypoint changes (as per the docs, it can), then there will still be a `Program` class in the same assembly, and you can then use that to get *to* that assembly in order to get the entrypoint for it, whichever type that may be located in. – Lasse V. Karlsen Jan 10 '22 at 12:29
  • @LasseV.Karlsen those are alternatives. They suggest to do one or another . – Guru Stron Jan 10 '22 at 13:26
  • @GuruStron, thank you for your answer to the OP's question. I've got a clarifying question. You said, "...make the tested project's internals visible to the test project for example by adding next property to csproj". Which csproj file? The one for the top level statement project or the unit test project? – Rod Feb 10 '22 at 01:47
  • @Rod yes, the one with top-level statement should describe another assembly(es) which should have access to it's internal methods. – Guru Stron Feb 10 '22 at 10:58