Reputation: 141
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
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
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
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