How to ingration test Console App attached to Visual Studio debugger - c#

I'm making a Console App and I want to create the kind of integration/component tests I'm used to for ASP.NET Core applications using WebApplicationFactory<>.
However, I haven't been able to find a way to test the Console App in an attached way that ensures I can set breakpoints in the Console App during debugging tests.
I have tried to hack something together using the following answer, but I didn't achieve breakpoints. By the time it's trying to attach, the process has already terminated.
https://stackoverflow.com/a/72075450
Is there a better way I'm overlooking?
Simplified version of my current code:
internal class Program
{
static async Task Main(string[] args)
{
var host = Host.CreateDefaultBuilder(args)
.UseConsoleLifetime()
.ConfigureServices((_, services) => services.AddHostedService<CustomHostedService>())
.Build();
await host.RunAsync();
}
}
internal class CustomHostedService : IHostedService
{
private readonly IHostApplicationLifetime _hostApplicationLifetime;
public CustomHostedService(IHostApplicationLifetime hostApplicationLifetime)
{
_hostApplicationLifetime = hostApplicationLifetime;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
// Actual code for the application
_hostApplicationLifetime.StopApplication();
}
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
internal class IntegrationTests
{
[Fact]
public void Executable()
{
Process.Start("{assemblyName}.exe {arguments}")
// Asserts
}
[Fact]
public void DotnetRun()
{
Process.Start("dotnet", "run --project {project_path} {arguments}");
// Asserts
}
}

If it's a console app run it with AutoHotKey, or SikuliX for wimforms or another system as a testing framework.
Selenium is popular for testing websites but console and winform are completely different they aren't designed for the WebApplicationFactory<> pattern.
You maybe able to do it though there's a reason you're unable to find examples of others going down this mismatching technology path as it's unorthodox and unsupported.
I'd try out AHK and SikuliX, both are free and reconsider attaching a debugger to the process - that's typically useful for troubleshooting systems with problems that can't be reproduced. Where as you're just running a console app with a bunch of logic code paths you can easily verify by reading the StdOut to various sequences of inputs.

Related

Serilog Not Outputting Anything

Using .Net 4.6 I have a static Serilog helper class - I've stripped down to the essentials as follows:
public static class SerilogHelper
{
private static ILogger log;
private static ILogger CreateLogger()
{
if (log == null)
{
string levelString = SSOSettingsFileManager.SSOSettingsFileReader.ReadString(
"BizTalk.Common", "serilog.minimum-level");
SerilogLevel level = (SerilogLevel)Enum.Parse(typeof(SerilogLevel), levelString);
string conString = SSOSettingsFileManager.SSOSettingsFileReader.ReadString(
"BizTalk.Common", "serilog.connection-string");
var levelSwitch = new LoggingLevelSwitch();
levelSwitch.MinimumLevel = (Serilog.Events.LogEventLevel)level;
log = new LoggerConfiguration()
.MinimumLevel.ControlledBy(levelSwitch)
.WriteTo.MSSqlServer(connectionString: conString, tableName: "Serilog", autoCreateSqlTable: true)
.WriteTo.RollingFile("log-{Date}.txt")
.CreateLogger();
}
return log;
}
public static void WriteString(string content)
{
var logger = CreateLogger();
logger.Information(content);
}
I have the following unit test:
[TestMethod]
public void UN_TestSerilog1()
{
Common.Components.Helpers.SerilogHelper.WriteString("Simple logging");
}
I've stepped through the debugger to be sure that the "level" variable is being set correctly - it's an enum named "Debug" with value of 1.
Although the Sql Server table is created ok, I don't see any rows inserted or any log txt file.
I've also tried calling logger.Error(content) but still no output.
I've used the same helper code previously on a different site / project and it worked ok.
Where did I go wrong this time?
Serilog.Sinks.MSSqlServer is a "periodic batching" sink and by default, it waits 5 seconds before sending the logs to the database. If your test ends before the sink had a chance to write the messages to the database, they are simply lost...
You need to make sure you dispose the logger before your test runner ends, to force the sink to flush the logs to the database at that point. See Lifecycle of Loggers.
((IDisposable) logger).Dispose();
Of course, if you are sharing a static log instance across multiple tests, you can't just dispose the logger inside of a single test as that would mean the next test that runs won't have a logger to write to... In that case, you should look at your testing framework support for executing code once, before the test suite run starts, and once again, for when the a test suite run ends.
I'm guessing you are using MSTest (because of the TestMethod), so you probably want to look into AssemblyInitialize and AssemblyCleanup, which would give you the opportunity to initialize the logger for all tests, and clean up after all tests finished running...
You might be interested in other ideas for troubleshooting Serilog issues: Serilog MSSQL Sink doesn't write logs to database

How to stop/exit/terminate dotnet core HostBuilder console application programmatically?

I'm trying to create a dotnet core console app. The app is a simply utility app and should start, do its thing and exit.
It's easy to achieve using standard console application template generated by Visual Studio.
But these days we have HostBuilder which seems to be more attractive because it brings unifying experience with WebHostBuilder, DI, Loggin, etc.
But it looks like this approach only designed for the long running background services. It will start and sit forever until the external termination event (like Ctrl-C).
Is there a way to end this type of application from the inside?
You can use IHostApplicationLifetime to stop running of your application, you can access it from constructor and call StopApplication() method.
IHostApplicationLifetime _lifeTime;
public MyClass(IHostApplicationLifetime lifeTime)
{
_lifeTime = lifeTime;
}
then StopApplication()
public void Exit()
{
_lifeTime.StopApplication();
}
Edited to use IHostApplicationLifetime as IApplicationLifetime is deprected.
Even though you are using the HostBuilder to register all dependencies in your application, you don't have to use the IHost to execute a cmd line app. You can just execute your app via creating a service scope like so:
var hostbuilder = new HostBuilder();
builder.ConfigureServices(ConfigureServices); //Configure all services for your application here
var host = hostbuilder.Build();
using (var scope = host.Services.CreateScope())
{
var myAppService = scope.ServiceProvider.GetRequiredService<IMyAppServiceToRun>(); //Use IHost DI container to obtain instance of service to run & resolve all dependencies
await myAppService.StartAsync(); // Execute your task here
}
using (host)
host.StopAsync();
I had the same problem. I needed to terminate my web service. So I used the approach like the solution marked answer, but the service didn't terminate instantly he continued with commands. So I needed to return right after the exit.
my killing method:
private void Exit(IHostApplicationLifetime lifetime, ExitCode code)
{
Environment.ExitCode = (int)code; //just shows in debug
lifetime.StopApplication();
// add log if you want to see the exitCode
}
the Configure method inside the Startup.cs
public void Configure(IApplicationBuilder app, IHostApplicationLifetime lifetime)
{
// code
bool hasPendingMigrations = HasDatabasePendingMigrations(app);
if (hasPendingMigrations)
{
Exit(lifetime, ExitCode.HasPendingMigrations);
// we need to return explicit after exit
return;
}
// code
}
You need to return.
The ExitCode is a custom created enum.
Edit:
After some time I moved the seeding into the Program.cs of my project which allows me to add test data / seed the database before even starting the server.
1- Use the UseConsoleLifetime() while building the host in Program.cs
Program.cs:
Host.CreateDefaultBuilder(args).UseConsoleLifetime(opts => opts.SuppressStatusMessages = true);
2- Registered ApplicationStopped event. So that you can brute force terminate the app by calling Kill() method of the current process.
Startup.cs:
public void Configure(IHostApplicationLifetime appLifetime) {
appLifetime.ApplicationStarted.Register(() => {
Console.WriteLine("Press Ctrl+C to shut down.");
});
appLifetime.ApplicationStopped.Register(() => {
Console.WriteLine("Terminating application...");
System.Diagnostics.Process.GetCurrentProcess().Kill();
});
}
The following works as of .NET Core 5.x/6.x
I needed the ability to shut down a service running on my web host.
They don't provide the granular ability to choose a specific web service that is running & I was having to continually shut down my entire site. I wanted to be able to call a WebAPI method in my service & shut it down just the one service instead of having to stop IIS & restart.
Here's how you can add the ability to shutdown your service from your HomeController (or any other controller in your project).
In your HomeController add the following code:
public class HomeController : Controller
{
private IHostApplicationLifetime _lifeTime; // add private member
// add appLifetime parameter to constructor
public HomeController(ILogger<HomeController> logger, IHostApplicationLifetime appLifetime)
{
_logger = logger;
_lifeTime = appLifetime; // init the private member var
}
Now, we can add a method that you can access via JavaScript fetch() for example.
Further down in the HomeController add the following method.
[HttpGet("StopService")]
public ActionResult StopService(){
_lifeTime.StopApplication();
return new JsonResult(new {result="true",message="<YourServiceName> is shutting down."});
}
Now you can make a call to your service (even from the browser console window) like the following:
fetch("http://localhost:5243/StopService")
.then(result => result.json())
.then(data => console.log(data));
When that call completes you will see something like the following in your console.
If you want to protect the method (I suggest you do) from outsiders then you can simply add a pwd as a parameter to the StopService method & take a hashed pwd to insure the user who is calling it is allowed. I'll let you work out those details.

C# API library and logging to PowerShell's WriteVerbose()

I'm building a DLL in C# that I will be consuming with several different projects - so far, I know of a WPF application and a (binary) PowerShell module. Because the core business logic needs to be shared across multiple projects, I don't want the PowerShell module itself to contain the core logic. I'd just like to reference my primary library.
I'm struggling to figure out how to implement a clean logging solution in my core DLL that will be accessible via PowerShell's WriteVerbose() method. Without this, I can provide verbose output to PowerShell about PowerShell-specific things, but I can't provide any verbose output about "waiting for HTTP request" or other features that would be in the core DLL.
Here's a simple example of what I'm trying to do:
using System;
using System.Threading;
namespace CoreApp
{
public class AppObject
{
public AppObject() {}
public int DoStuffThatTakesForever()
{
// Assume logger is a logging object - could be an existing
// library like NLog, or I could write it myself
logger.Info("Doing step 1");
Thread.Sleep(5000);
logger.Info("Doing step 2");
Thread.Sleep(5000);
logger.Info("Doing step 3");
Random r = new Random();
r.Next(0, 10);
}
}
}
////////////////////////////////////////////////////////////
// Separate VS project that references the CoreApp project
using System.Management.Automation;
using CoreApp;
namespace CoreApp.PowerShell
{
[Cmdlet(VerbsCommon.Invoke, "ThingWithAppObject"]
[OutputType(typeof(Int32))]
public class InvokeThingWithAppObject : Cmdlet
{
[Parameter(Position = 0)]
public AppObject InputObject {get; set;}
protected override void ProcessRecord()
{
// Here I want to be able to send the logging phrases,
// "Doing step 1", "Doing step 2", etc., to PowerShell's
// verbose stream (probably using Cmdlet.WriteVerbose() )
int result = InputObject.DoStuffThatTakesForever();
WriteObject(result);
}
}
}
How can I provide verbose PowerShell verbose output without tightly binding the core library with the PowerShell module?
I'm definitely open to other solutions, but here's how I ended up solving it:
In the core library, I created an ILogger interface with methods for Info, Verbose, Warn, etc. I created a DefaultLogger class that implemented that logger (by writing everything to the attached debugger), and I gave this class a static singleton instance.
In each method that I wanted logged, I added an optional ILogger parameter, and added a line to use the default logger if necessary. The method definitions now look like this:
public int DoSomething(ILogger logger = null)
{
logger = logger ?? MyAppLogger.Singleton;
// Rest of the code
Random r = new Random();
return r.Next(0, 10);
}
I had to do this for each method because the PSCmdlet.WriteVerbose() method expects to be called from the currently running cmdlet. I couldn't create a persistent class variable to hold a logger object because each time the user ran a cmdlet, the PSCmdlet object (with the WriteVerbose method I need) would change.
Finally, I went back to the PowerShell consumer project. I implemented the ILogger class in my base cmdlet class:
public class MyCmdletBase : PSCmdlet, ILogger
{
public void Verbose(string message) => WriteVerbose(message);
public void Debug(string message) => WriteDebug(message);
// etc.
}
Now it's trivial to pass the current cmdlet as an ILogger instance when calling a method from the core library:
[Cmdlet(VerbsCommon.Invoke, "ThingWithAppObject"]
[OutputType(typeof(Int32))]
public class InvokeThingWithAppObject : MyCmdletBase
{
[Parameter(Mandatory = true, Position = 0)]
public AppObject InputObject {get; set;}
protected override void ProcessRecord()
{
int result = InputObject.DoSomething(this);
WriteObject(result);
}
}
In a different project, I'll need to write some kind of "log adapter" to implement the ILogger interface and write log entries to NLog (or whatever logging library I end up with).
The only other hiccup I ran into is that WriteVerbose(), WriteDebug(), etc. cannot be called from a different thread than the main thread the cmdlet is running on. This was a significant problem, since I'm making async Web requests, but after banging my head on the wall I decided to just block and run the Web requests synchronously instead. I'll probably end up implementing both a synchronous and an async version of each Web-based function in the core library.
This approach feels a bit dirty to me, but it works brilliantly.

QueueBackgroundWorkItem from Microsoft.VisualStudio.TestTools.UnitTesting

I have a Web App that does some processing in the background via QueueBackgroundWorkItem.
I'm wiring up unit tests for functionality in the app and when it attempts to invoke this I get the following error:
System.InvalidOperationException occurred
HResult=-2146233079
Message=Operation is not valid due to the current state of the object.
Source=System.Web
StackTrace:
at System.Web.Hosting.HostingEnvironment.QueueBackgroundWorkItem(Func`2 workItem)
Comparing the environments between when this gets invoked from a unit test vs when it gets invoked as part of a running web server, I see that the AppDomain / HostingEnvironment are different.
Without doing a full web app deployment for testing, is there a way to structure the unit test so that the background work item can run in the proper context?
Preferably without changing the existing target code, just by changing the test code - and if that isn't possible, maybe use IOC to run the background work item in an appropriate background thread depending on its context.
update
This works, although probably there is a more elegant way to do it. Basically only runs it background if invoked from a hosted environment:
private void GetSomeData(SomeCriteria criteria, Dictionary<int, List<Tuple<int, string>>> someParam)
{
if (System.Web.Hosting.HostingEnvironment.IsHosted)
{
System.Web.Hosting.HostingEnvironment.QueueBackgroundWorkItem((token) =>
{
GenerateSomeData(token, criteria, someParam);
});
}
else
{
CancellationToken token = new CancellationToken();
GenerateSomeData(token, criteria, someParam);
}
}
I know it's kinda ugly, but I ended up creating my own helper class
public static class BackgroundWorkItemX
{
public static void QueueBackgroundWorkItem(Action<CancellationToken> workItem)
{
try
{
HostingEnvironment.QueueBackgroundWorkItem(workItem);
}
catch (InvalidOperationException)
{
workItem.Invoke(new CancellationToken());
}
}
}
And changed all references to QueueBackgroundWorkItem to this class

Azure WebJob - No functions found. Try making job classes public and methods public static

I'm using an Azure WebJob, but now I get the following error message:
No functions found. Try making job classes public and methods public static.
My code is so simple:
static void Main()
{
var host = new JobHost();
host.RunAndBlock();
}
public static async Task BlobTrigger(
[BlobTrigger("test/{name}")] Microsoft.WindowsAzure.Storage.Blob.CloudBlockBlob input,
TextWriter log)
{
//code
}
Also, I create a zip file from my debug folder and upload it, and the job is configure to run continuously.
so sorry, the error is so simple, I added the access public to the class and it's fine (I'm using the final version of web jobs here), but I have some jobs with the webjobs prerelease and the public it's not necessary.
Thanks to all, regards.

Categories

Resources