Add XunitLoggerProvider (#81)

* Add XunitLoggerProvider

* Avoid logging to stale ITestOutputHelper in fixture disposal

* Use LoggedTest base class everywhere an ILoggerFactory is used
This commit is contained in:
Stephen Halter 2025-03-25 05:53:55 -07:00 • committed by GitHub
commit 60ea96a97c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 178 additions and 86 deletions

View file

@ -1,18 +1,18 @@
using ModelContextProtocol.Protocol.Transport;
using ModelContextProtocol.Protocol.Types;
using ModelContextProtocol.Server;
using Microsoft.Extensions.Logging.Abstractions;
using ModelContextProtocol.Tests.Utils;
using Moq;
namespace ModelContextProtocol.Tests.Server;
public class McpServerFactoryTests
public class McpServerFactoryTests : LoggedTest
{
private readonly Mock<IServerTransport> _serverTransport;
private readonly McpServerOptions _options;
private readonly IServiceProvider _serviceProvider;
public McpServerFactoryTests()
public McpServerFactoryTests(ITestOutputHelper testOutputHelper)
: base(testOutputHelper)
{
_serverTransport = new Mock<IServerTransport>();
_options = new McpServerOptions
@ -21,14 +21,13 @@ public class McpServerFactoryTests
ProtocolVersion = "1.0",
InitializationTimeout = TimeSpan.FromSeconds(30)
};
_serviceProvider = new Mock<IServiceProvider>().Object;
}
[Fact]
public async Task Create_Should_Initialize_With_Valid_Parameters()
{
// Arrange & Act
await using IMcpServer server = McpServerFactory.Create(_serverTransport.Object, _options, NullLoggerFactory.Instance);
await using IMcpServer server = McpServerFactory.Create(_serverTransport.Object, _options, LoggerFactory);
// Assert
Assert.NotNull(server);
@ -38,13 +37,13 @@ public class McpServerFactoryTests
public void Constructor_Throws_For_Null_ServerTransport()
{
// Arrange, Act & Assert
Assert.Throws<ArgumentNullException>("serverTransport", () => McpServerFactory.Create(null!, _options, NullLoggerFactory.Instance));
Assert.Throws<ArgumentNullException>("serverTransport", () => McpServerFactory.Create(null!, _options, LoggerFactory));
}
[Fact]
public void Constructor_Throws_For_Null_Options()
{
// Arrange, Act & Assert
Assert.Throws<ArgumentNullException>("serverOptions", () => McpServerFactory.Create(_serverTransport.Object, null!, NullLoggerFactory.Instance));
Assert.Throws<ArgumentNullException>("serverOptions", () => McpServerFactory.Create(_serverTransport.Object, null!, LoggerFactory));
}
}

View file

@ -11,20 +11,18 @@ using System.Reflection;
namespace ModelContextProtocol.Tests.Server;
public class McpServerTests
public class McpServerTests : LoggedTest
{
private readonly Mock<IServerTransport> _serverTransport;
private readonly Mock<ILoggerFactory> _loggerFactory;
private readonly Mock<ILogger> _logger;
private readonly McpServerOptions _options;
private readonly IServiceProvider _serviceProvider;
public McpServerTests()
public McpServerTests(ITestOutputHelper testOutputHelper)
: base(testOutputHelper)
{
_serverTransport = new Mock<IServerTransport>();
_loggerFactory = new Mock<ILoggerFactory>();
_logger = new Mock<ILogger>();
_loggerFactory.Setup(f => f.CreateLogger(It.IsAny<string>())).Returns(_logger.Object);
_options = CreateOptions();
_serviceProvider = new Mock<IServiceProvider>().Object;
}
@ -44,7 +42,7 @@ public class McpServerTests
public async Task Constructor_Should_Initialize_With_Valid_Parameters()
{
// Arrange & Act
await using var server = McpServerFactory.Create(_serverTransport.Object, _options, _loggerFactory.Object, _serviceProvider);
await using var server = McpServerFactory.Create(_serverTransport.Object, _options, LoggerFactory, _serviceProvider);
// Assert
Assert.NotNull(server);
@ -54,14 +52,14 @@ public class McpServerTests
public void Constructor_Throws_For_Null_Transport()
{
// Arrange, Act & Assert
Assert.Throws<ArgumentNullException>(() => McpServerFactory.Create(null!, _options, _loggerFactory.Object, _serviceProvider));
Assert.Throws<ArgumentNullException>(() => McpServerFactory.Create(null!, _options, LoggerFactory, _serviceProvider));
}
[Fact]
public void Constructor_Throws_For_Null_Options()
{
// Arrange, Act & Assert
Assert.Throws<ArgumentNullException>(() => McpServerFactory.Create(_serverTransport.Object, null!, _loggerFactory.Object, _serviceProvider));
Assert.Throws<ArgumentNullException>(() => McpServerFactory.Create(_serverTransport.Object, null!, LoggerFactory, _serviceProvider));
}
[Fact]
@ -78,7 +76,7 @@ public class McpServerTests
public async Task Constructor_Does_Not_Throw_For_Null_ServiceProvider()
{
// Arrange & Act
await using var server = McpServerFactory.Create(_serverTransport.Object, _options, _loggerFactory.Object, null);
await using var server = McpServerFactory.Create(_serverTransport.Object, _options, LoggerFactory, null);
// Assert
Assert.NotNull(server);
@ -88,7 +86,7 @@ public class McpServerTests
public async Task StartAsync_Should_Throw_InvalidOperationException_If_Already_Initializing()
{
// Arrange
await using var server = McpServerFactory.Create(_serverTransport.Object, _options, _loggerFactory.Object, _serviceProvider);
await using var server = McpServerFactory.Create(_serverTransport.Object, _options, LoggerFactory, _serviceProvider);
var task = server.StartAsync(TestContext.Current.CancellationToken);
// Act & Assert
@ -101,7 +99,7 @@ public class McpServerTests
public async Task StartAsync_Should_Do_Nothing_If_Already_Initialized()
{
// Arrange
await using var server = McpServerFactory.Create(_serverTransport.Object, _options, _loggerFactory.Object, _serviceProvider);
await using var server = McpServerFactory.Create(_serverTransport.Object, _options, LoggerFactory, _serviceProvider);
SetInitialized(server, true);
await server.StartAsync(TestContext.Current.CancellationToken);
@ -114,7 +112,7 @@ public class McpServerTests
public async Task StartAsync_ShouldStartListening()
{
// Arrange
await using var server = McpServerFactory.Create(_serverTransport.Object, _options, _loggerFactory.Object, _serviceProvider);
await using var server = McpServerFactory.Create(_serverTransport.Object, _options, LoggerFactory, _serviceProvider);
// Act
await server.StartAsync(TestContext.Current.CancellationToken);
@ -127,7 +125,7 @@ public class McpServerTests
public async Task StartAsync_Sets_Initialized_After_Transport_Responses_Initialized_Notification()
{
await using var transport = new TestServerTransport();
await using var server = McpServerFactory.Create(transport, _options, _loggerFactory.Object, _serviceProvider);
await using var server = McpServerFactory.Create(transport, _options, LoggerFactory, _serviceProvider);
await server.StartAsync(TestContext.Current.CancellationToken);
@ -147,7 +145,7 @@ public class McpServerTests
public async Task RequestSamplingAsync_Should_Throw_McpServerException_If_Client_Does_Not_Support_Sampling()
{
// Arrange
await using var server = McpServerFactory.Create(_serverTransport.Object, _options, _loggerFactory.Object, _serviceProvider);
await using var server = McpServerFactory.Create(_serverTransport.Object, _options, LoggerFactory, _serviceProvider);
SetClientCapabilities(server, new ClientCapabilities());
var action = () => server.RequestSamplingAsync(new CreateMessageRequestParams { Messages = [] }, CancellationToken.None);
@ -161,7 +159,7 @@ public class McpServerTests
{
// Arrange
await using var transport = new TestServerTransport();
await using var server = McpServerFactory.Create(transport, _options, _loggerFactory.Object, _serviceProvider);
await using var server = McpServerFactory.Create(transport, _options, LoggerFactory, _serviceProvider);
SetClientCapabilities(server, new ClientCapabilities { Sampling = new SamplingCapability() });
await server.StartAsync(TestContext.Current.CancellationToken);
@ -179,7 +177,7 @@ public class McpServerTests
public async Task RequestRootsAsync_Should_Throw_McpServerException_If_Client_Does_Not_Support_Roots()
{
// Arrange
await using var server = McpServerFactory.Create(_serverTransport.Object, _options, _loggerFactory.Object, _serviceProvider);
await using var server = McpServerFactory.Create(_serverTransport.Object, _options, LoggerFactory, _serviceProvider);
SetClientCapabilities(server, new ClientCapabilities());
// Act & Assert
@ -191,7 +189,7 @@ public class McpServerTests
{
// Arrange
await using var transport = new TestServerTransport();
await using var server = McpServerFactory.Create(transport, _options, _loggerFactory.Object, _serviceProvider);
await using var server = McpServerFactory.Create(transport, _options, LoggerFactory, _serviceProvider);
SetClientCapabilities(server, new ClientCapabilities { Roots = new RootsCapability() });
await server.StartAsync(TestContext.Current.CancellationToken);
@ -208,7 +206,7 @@ public class McpServerTests
[Fact]
public async Task Throws_Exception_If_Not_Connected()
{
await using var server = McpServerFactory.Create(_serverTransport.Object, _options, _loggerFactory.Object, _serviceProvider);
await using var server = McpServerFactory.Create(_serverTransport.Object, _options, LoggerFactory, _serviceProvider);
SetClientCapabilities(server, new ClientCapabilities { Roots = new RootsCapability() });
_serverTransport.SetupGet(t => t.IsConnected).Returns(false);
@ -555,7 +553,7 @@ public class McpServerTests
var options = CreateOptions(serverCapabilities);
configureOptions?.Invoke(options);
await using var server = McpServerFactory.Create(transport, options, _loggerFactory.Object, _serviceProvider);
await using var server = McpServerFactory.Create(transport, options, LoggerFactory, _serviceProvider);
await server.StartAsync();
@ -587,7 +585,7 @@ public class McpServerTests
await using var transport = new TestServerTransport();
var options = CreateOptions(serverCapabilities);
Assert.Throws<McpServerException>(() => McpServerFactory.Create(transport, options, _loggerFactory.Object, _serviceProvider));
Assert.Throws<McpServerException>(() => McpServerFactory.Create(transport, options, LoggerFactory, _serviceProvider));
}
[Fact]