Skip to content

Upgrade to xunit.v3 #8

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
<PackageVersion Include="Serilog.Sinks.Debug" Version="3.0.0" />
<PackageVersion Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageVersion Include="System.Linq.AsyncEnumerable" Version="$(System10Version)" />
<PackageVersion Include="xunit" Version="2.9.2" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.0.0" />
<PackageVersion Include="xunit.v3" Version="1.1.0" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.0.2" />
</ItemGroup>
</Project>
2 changes: 2 additions & 0 deletions ModelContextProtocol.sln
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B6FB2B28-D5DE-4654-BE9A-45E305DE4852}"
ProjectSection(SolutionItems) = preProject
Directory.Build.props = Directory.Build.props
Directory.Packages.props = Directory.Packages.props
global.json = global.json
LICENSE = LICENSE
logo.png = logo.png
nuget.config = nuget.config
README.MD = README.MD
version.json = version.json
EndProjectSection
Expand Down
1 change: 0 additions & 1 deletion tests/ModelContextProtocol.TestServer/Program.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Text;
using System.Text.Json;
using ModelContextProtocol.Protocol.Messages;
using ModelContextProtocol.Protocol.Transport;
using ModelContextProtocol.Protocol.Types;
Expand Down
36 changes: 17 additions & 19 deletions tests/ModelContextProtocol.Tests/Client/McpClientFactoryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,34 +17,28 @@ public class McpClientFactoryTests
[Fact]
public async Task CreateAsync_WithInvalidArgs_Throws()
{
await Assert.ThrowsAsync<ArgumentNullException>("serverConfig", () => McpClientFactory.CreateAsync((McpServerConfig)null!, _defaultOptions));
await Assert.ThrowsAsync<ArgumentNullException>("serverConfig", () => McpClientFactory.CreateAsync((McpServerConfig)null!, _defaultOptions, cancellationToken: TestContext.Current.CancellationToken));

await Assert.ThrowsAsync<ArgumentNullException>("clientOptions", () => McpClientFactory.CreateAsync(
new McpServerConfig()
await Assert.ThrowsAsync<ArgumentNullException>("clientOptions", () => McpClientFactory.CreateAsync(new McpServerConfig()
{
Name = "name",
Id = "id",
TransportType = TransportTypes.StdIo,
}, (McpClientOptions)null!));
}, (McpClientOptions)null!, cancellationToken: TestContext.Current.CancellationToken));

await Assert.ThrowsAsync<ArgumentException>("serverConfig", () => McpClientFactory.CreateAsync(
new McpServerConfig()
await Assert.ThrowsAsync<ArgumentException>("serverConfig", () => McpClientFactory.CreateAsync(new McpServerConfig()
{
Name = "name",
Id = "id",
TransportType = "somethingunsupported",
},
_defaultOptions));
}, _defaultOptions, cancellationToken: TestContext.Current.CancellationToken));

await Assert.ThrowsAsync<InvalidOperationException>(() => McpClientFactory.CreateAsync(
new McpServerConfig()
await Assert.ThrowsAsync<InvalidOperationException>(() => McpClientFactory.CreateAsync(new McpServerConfig()
{
Name = "name",
Id = "id",
TransportType = TransportTypes.StdIo,
},
_defaultOptions,
(_, __) => null!));
}, _defaultOptions, (_, __) => null!, cancellationToken: TestContext.Current.CancellationToken));
}

[Fact]
Expand All @@ -68,7 +62,8 @@ public async Task CreateAsync_WithValidStdioConfig_CreatesNewClient()
await using var client = await McpClientFactory.CreateAsync(
serverConfig,
_defaultOptions,
(_, __) => new NopTransport());
(_, __) => new NopTransport(),
cancellationToken: TestContext.Current.CancellationToken);

// Assert
Assert.NotNull(client);
Expand All @@ -91,7 +86,8 @@ public async Task CreateAsync_WithNoTransportOptions_CreatesNewClient()
await using var client = await McpClientFactory.CreateAsync(
serverConfig,
_defaultOptions,
(_, __) => new NopTransport());
(_, __) => new NopTransport(),
cancellationToken: TestContext.Current.CancellationToken);

// Assert
Assert.NotNull(client);
Expand All @@ -114,7 +110,8 @@ public async Task CreateAsync_WithValidSseConfig_CreatesNewClient()
await using var client = await McpClientFactory.CreateAsync(
serverConfig,
_defaultOptions,
(_, __) => new NopTransport());
(_, __) => new NopTransport(),
cancellationToken: TestContext.Current.CancellationToken);

// Assert
Assert.NotNull(client);
Expand Down Expand Up @@ -144,7 +141,8 @@ public async Task CreateAsync_WithSse_CreatesCorrectTransportOptions()
await using var client = await McpClientFactory.CreateAsync(
serverConfig,
_defaultOptions,
(_, __) => new NopTransport());
(_, __) => new NopTransport(),
cancellationToken: TestContext.Current.CancellationToken);

// Assert
Assert.NotNull(client);
Expand All @@ -171,7 +169,7 @@ public async Task McpFactory_WithInvalidTransportOptions_ThrowsFormatException(s
};

// act & assert
await Assert.ThrowsAsync<ArgumentException>(() => McpClientFactory.CreateAsync(config, _defaultOptions));
await Assert.ThrowsAsync<ArgumentException>(() => McpClientFactory.CreateAsync(config, _defaultOptions, cancellationToken: TestContext.Current.CancellationToken));
}

private sealed class NopTransport : IClientTransport
Expand All @@ -191,7 +189,7 @@ public Task SendMessageAsync(IJsonRpcMessage message, CancellationToken cancella
{
switch (message)
{
case JsonRpcRequest request:
case JsonRpcRequest:
_channel.Writer.TryWrite(new JsonRpcResponse
{
Id = ((JsonRpcRequest)message).Id,
Expand Down
75 changes: 41 additions & 34 deletions tests/ModelContextProtocol.Tests/ClientIntegrationTests.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
using ModelContextProtocol.Client;
using ModelContextProtocol.Configuration;
using ModelContextProtocol.Protocol.Messages;
using ModelContextProtocol.Protocol.Transport;
using ModelContextProtocol.Protocol.Types;
using Microsoft.Extensions.AI;
using OpenAI;
using ModelContextProtocol.Protocol.Types;
using ModelContextProtocol.Protocol.Messages;
using System.Text.Json;
using ModelContextProtocol.Configuration;
using ModelContextProtocol.Protocol.Transport;
using Xunit.Sdk;

namespace ModelContextProtocol.Tests;

Expand Down Expand Up @@ -61,8 +62,8 @@ public async Task ListTools_Stdio(string clientId)

// act
await using var client = await _fixture.CreateClientAsync(clientId);
var tools = await client.ListToolsAsync().ToListAsync();
var aiFunctions = await client.GetAIFunctionsAsync();
var tools = await client.ListToolsAsync(TestContext.Current.CancellationToken).ToListAsync(TestContext.Current.CancellationToken);
var aiFunctions = await client.GetAIFunctionsAsync(TestContext.Current.CancellationToken);

// assert
Assert.NotEmpty(tools);
Expand Down Expand Up @@ -102,9 +103,9 @@ public async Task CallTool_Stdio_ViaAIFunction_EchoServer(string clientId)

// act
await using var client = await _fixture.CreateClientAsync(clientId);
var aiFunctions = await client.GetAIFunctionsAsync();
var aiFunctions = await client.GetAIFunctionsAsync(TestContext.Current.CancellationToken);
var echo = aiFunctions.Single(t => t.Name == "echo");
var result = await echo.InvokeAsync([new KeyValuePair<string, object?>("message", "Hello MCP!")]);
var result = await echo.InvokeAsync([new KeyValuePair<string, object?>("message", "Hello MCP!")], TestContext.Current.CancellationToken);

// assert
Assert.NotNull(result);
Expand All @@ -119,7 +120,7 @@ public async Task ListPrompts_Stdio(string clientId)

// act
await using var client = await _fixture.CreateClientAsync(clientId);
var prompts = await client.ListPromptsAsync().ToListAsync();
var prompts = await client.ListPromptsAsync(TestContext.Current.CancellationToken).ToListAsync(TestContext.Current.CancellationToken);

// assert
Assert.NotEmpty(prompts);
Expand Down Expand Up @@ -251,7 +252,7 @@ public async Task SubscribeResource_Stdio()
await client.SubscribeToResourceAsync("test://static/resource/1", CancellationToken.None);

// notifications happen every 5 seconds, so we wait for 10 seconds to ensure we get at least one notification
await Task.Delay(10000);
await Task.Delay(10000, TestContext.Current.CancellationToken);

// assert
Assert.True(counter > 0);
Expand All @@ -276,17 +277,17 @@ public async Task UnsubscribeResource_Stdio()
await client.SubscribeToResourceAsync("test://static/resource/1", CancellationToken.None);

// notifications happen every 5 seconds, so we wait for 10 seconds to ensure we get at least one notification
await Task.Delay(10000);
await Task.Delay(10000, TestContext.Current.CancellationToken);

// reset counter
int counterAfterSubscribe = counter;

// unsubscribe
await client.UnsubscribeFromResourceAsync("test://static/resource/1", CancellationToken.None);
counter = 0;

// notifications happen every 5 seconds, so we wait for 10 seconds to ensure we would've gotten at least one notification
await Task.Delay(10000);
await Task.Delay(10000, TestContext.Current.CancellationToken);

// assert
Assert.True(counterAfterSubscribe > 0);
Expand Down Expand Up @@ -340,7 +341,7 @@ public async Task GetCompletion_Stdio_PromptReference(string clientId)
[Theory]
[MemberData(nameof(GetClients))]
public async Task Sampling_Stdio(string clientId)
{
{
// Set up the sampling handler
int samplingHandlerCalls = 0;
await using var client = await _fixture.CreateClientAsync(clientId, new()
Expand Down Expand Up @@ -375,8 +376,8 @@ public async Task Sampling_Stdio(string clientId)
{
["prompt"] = "Test prompt",
["maxTokens"] = 100
}
);
},
TestContext.Current.CancellationToken);

// assert
Assert.NotNull(result);
Expand Down Expand Up @@ -423,8 +424,8 @@ public async Task Notifications_Stdio(string clientId)
await using var client = await _fixture.CreateClientAsync(clientId);

// Verify we can send notifications without errors
await client.SendNotificationAsync(NotificationMethods.RootsUpdatedNotification);
await client.SendNotificationAsync("test/notification", new { test = true });
await client.SendNotificationAsync(NotificationMethods.RootsUpdatedNotification, cancellationToken: TestContext.Current.CancellationToken);
await client.SendNotificationAsync("test/notification", new { test = true }, TestContext.Current.CancellationToken);

// assert
// no response to check, if no exception is thrown, it's a success
Expand Down Expand Up @@ -452,13 +453,17 @@ public async Task CallTool_Stdio_MemoryServer()
ClientInfo = new() { Name = "IntegrationTestClient", Version = "1.0.0" }
};

await using var client = await McpClientFactory.CreateAsync(serverConfig, clientOptions, loggerFactory: _fixture.LoggerFactory);
await using var client = await McpClientFactory.CreateAsync(
serverConfig,
clientOptions,
loggerFactory: _fixture.LoggerFactory,
cancellationToken: TestContext.Current.CancellationToken);

// act
var result = await client.CallToolAsync(
"read_graph",
[]
);
[],
TestContext.Current.CancellationToken);

// assert
Assert.NotNull(result);
Expand All @@ -471,14 +476,14 @@ public async Task CallTool_Stdio_MemoryServer()
[Fact]
public async Task GetAIFunctionsAsync_UsingEverythingServer_ToolsAreProperlyCalled()
{
if (s_openAIKey is null)
{
return; // Skip the test if the OpenAI key is not provided
}
SkipTestIfNoOpenAIKey();

// Get the MCP client and tools from it.
await using var client = await McpClientFactory.CreateAsync(_fixture.EverythingServerConfig, _fixture.DefaultOptions); ;
var mappedTools = await client.GetAIFunctionsAsync();
await using var client = await McpClientFactory.CreateAsync(
_fixture.EverythingServerConfig,
_fixture.DefaultOptions,
cancellationToken: TestContext.Current.CancellationToken);
var mappedTools = await client.GetAIFunctionsAsync(TestContext.Current.CancellationToken);

// Create the chat client.
using IChatClient chatClient = new OpenAIClient(s_openAIKey).AsChatClient("gpt-4o-mini")
Expand All @@ -495,7 +500,7 @@ public async Task GetAIFunctionsAsync_UsingEverythingServer_ToolsAreProperlyCall
messages.Add(new(ChatRole.User, "Please call the echo tool with the string 'Hello MCP!' and output the response ad verbatim."));

// Call the chat client
var response = await chatClient.GetResponseAsync(messages, new() { Tools = [.. mappedTools], Temperature = 0 });
var response = await chatClient.GetResponseAsync(messages, new() { Tools = [.. mappedTools], Temperature = 0 }, TestContext.Current.CancellationToken);

// Assert
Assert.Contains("Echo: Hello MCP!", response.Text);
Expand All @@ -504,10 +509,7 @@ public async Task GetAIFunctionsAsync_UsingEverythingServer_ToolsAreProperlyCall
[Fact]
public async Task SamplingViaChatClient_RequestResponseProperlyPropagated()
{
if (s_openAIKey is null)
{
return; // Skip the test if the OpenAI key is not provided
}
SkipTestIfNoOpenAIKey();

await using var client = await McpClientFactory.CreateAsync(_fixture.EverythingServerConfig, new()
{
Expand All @@ -519,17 +521,22 @@ public async Task SamplingViaChatClient_RequestResponseProperlyPropagated()
SamplingHandler = new OpenAIClient(s_openAIKey).AsChatClient("gpt-4o-mini").CreateSamplingHandler(),
},
},
});
}, cancellationToken: TestContext.Current.CancellationToken);

var result = await client.CallToolAsync("sampleLLM", new()
{
["prompt"] = "In just a few words, what is the most famous tower in Paris?",
});
}, TestContext.Current.CancellationToken);

Assert.NotNull(result);
Assert.NotEmpty(result.Content);
Assert.Equal("text", result.Content[0].Type);
Assert.Contains("LLM sampling result:", result.Content[0].Text);
Assert.Contains("Eiffel", result.Content[0].Text);
}

private static void SkipTestIfNoOpenAIKey()
{
Assert.SkipWhen(s_openAIKey is null, "No OpenAI key provided. Skipping test.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Moq" />
<PackageReference Include="System.Linq.AsyncEnumerable" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.v3" />
<PackageReference Include="xunit.runner.visualstudio">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
Expand Down
Loading