Dziugas Vysniauskas
Dziugas Vysniauskas

Reputation: 141

How to log TestServer output to the test console

I'm currently writing an integration test (https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-5.0) for my ASP .Net Core 5 REST API. The API is using Serilog for logging (with the static Serilog Logger). I am running tests with NUnit, Visual Studio 2019, Resharper.

I want all the messages, that are logged during the runtime of the API code, to be visible in the test console output.
For example, if this controller method is called:

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Serilog;

namespace My.Crazy.Api.Controllers
{    
    public sealed class WheelsController : Controller
    {
        [HttpGet("getwheels")]        
        public async Task<IActionResult> Get()
        {            
            Log.Error("An extremely urgent error");         
            return Ok();
        }
    }
}

I expect the "An extremely urgent error" message to be shown in the test console.
However, this is not happening.

Here is my TestServer setup:

[OneTimeSetUp]
public async Task Setup()
{            
    var hostBuilder = new HostBuilder()
        .ConfigureWebHost(webHost =>
        {
            webHost.UseTestServer();
            webHost.UseStartup<Startup>();  // Startup is the API project's Startup class
    
            Log.Logger = new LoggerConfiguration().WriteTo.Console().CreateLogger();
        });

    var host = await hostBuilder.StartAsync();
    
    _client = host.GetTestClient();
}  

[Test]
public async Task FirstTest() 
{
    var response = await _client.GetAsync("getwheels");
}

I have also tried logging with a custom Sink:

...
// in the test setup
Log.Logger = new LoggerConfiguration().WriteTo.Sink(new CustomSink()).CreateLogger();
...

public class CustomSink : ILogEventSink
{
    public void Emit(LogEvent logEvent)
    {
        var message = logEvent.RenderMessage();
        Console.WriteLine(message);
    }
}

This does not work as well. However, I have confirmed that the Emit method is being invoked when API code logs any message.

Finally, I have tried using a File output:

Log.Logger = new LoggerConfiguration().WriteTo.File("C:\\temp\\test_output.txt").CreateLogger();

which worked as expected. However, I still want to log in the console.

Is this possible?

Using anything else for Serilog or NUnit is unfortunately not an option.

Upvotes: 13

Views: 5445

Answers (3)

Oleksiy
Oleksiy

Reputation: 51

You can try this library: https://www.nuget.org/packages/Serilog.Sinks.NUnit/

Easy to use:

var log = new LoggerConfiguration().WriteTo.NUnitOutput().CreateLogger();

Or

builder.Host.UseSerilog((context, configuration) =>
        {
            configuration.ReadFrom.Configuration(context.Configuration);
            if (builder.Environment.IsDevelopment())
            {
                configuration.WriteTo.NUnitOutput();
            }
        });

Upvotes: 0

Velimir
Velimir

Reputation: 762

I had the same problem. After days of digging, I found a workaround with the initialization of the test server. The key is in setting to true the PreserveExecutionContext which is by default false. Setting it to true brings the logs to the test output. False - no server logs are visible, only client ones.

    var path = Assembly.GetAssembly(typeof(MyTestServer))?.Location;

    var directoryName = Path.GetDirectoryName(path);

    if (directoryName == null)
        throw new InvalidOperationException("Cannot obtain startup directory name");

    var hostBuilder = new WebHostBuilder()
        .UseContentRoot(directoryName)
        .ConfigureAppConfiguration(
            configurationBuilder => configurationBuilder.AddJsonFile("appsettings.json", false))
        .UseStartup<Startup>()
        .ConfigureTestServices(services =>
        {
            //adding mock services here
        });

    server = new TestServer(hostBuilder) 
    {
        //set this to true!!!
        PreserveExecutionContext = true 
    };

Note: we're running these tests (and the system under test) on .NET7. I am not sure whether this makes any difference.

Upvotes: 7

ZeSzymi
ZeSzymi

Reputation: 126

So I would try with a custom logger provider with logger:

LoggerProvider:

public class NUnitLoggerProvider : ILoggerProvider
{
    public ILogger CreateLogger(string categoryName)
    {
        return new NUnitLogger();
    }

    public void Dispose()
    {
    }
}

Logger:

public class NUnitLogger : ILogger, IDisposable
{
    public void Dispose()
    {
    }

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception,
        Func<TState, Exception, string> formatter) {
        var message = formatter(state, exception);
        Debug.WriteLine(message);
    }

    public bool IsEnabled(LogLevel logLevel) => true;

    public IDisposable BeginScope<TState>(TState state) => this;
}    

Then in the test file:

var hostBuilder = new HostBuilder()
            .ConfigureWebHost(webHost =>
            {
                webHost.UseTestServer()
                    .UseStartup<TestStartup>()
                    .ConfigureLogging((hostBuilderContext, logging) =>
                     {
                         logging.Services.AddSingleton<ILoggerProvider, NUnitLoggerProvider>();
                     });
            });            

And instead of Debug.WriteLine(message) you can use something else to log to.

Upvotes: 2

Related Questions